android开发,CameraHelperFace人脸识别功能,java语言版本(网上下载的都是kotlin版本)

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
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.CaptureFailure;
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.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import android.view.TextureView;

import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;

import com.gvm.three.View.AutoFitTextureView;
import com.gvm.three.View.MyToast;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import static com.sun.activation.registries.LogSupport.log;

public class CameraHelperFace {

    Activity mActivity;
    private AutoFitTextureView mTextureView;

    int PREVIEW_WIDTH = 1080;                                        //预览的宽度
    int PREVIEW_HEIGHT = 1440;                                      //预览的高度
    int SAVE_WIDTH = 720;                                       //保存图片的宽度
    int SAVE_HEIGHT = 1280;                                       //保存图片的高度

    private CameraManager mCameraManager;
    private ImageReader mImageReader = null;
    private CameraDevice mCameraDevice = null;
    private CameraCaptureSession mCameraCaptureSession = null;

    private String mCameraId = "0";
    private CameraCharacteristics mCameraCharacteristics;

    private int mCameraSensorOrientation = 0;                                            //摄像头方向
    private int mCameraFacing = CameraCharacteristics.LENS_FACING_BACK;              //默认使用后置摄像头
    private int mDisplayRotation =0;
    private int mFaceDetectMode = CaptureResult.STATISTICS_FACE_DETECT_MODE_OFF;    //人脸检测模式

    private boolean canTakePic = true;                                                       //是否可以拍照
    private boolean canExchangeCamera = false;                                               //是否可以切换摄像头
    private boolean openFaceDetect = true;                                                   //是否开启人脸检测
    private Matrix mFaceDetectMatrix = new Matrix();                                            //人脸检测坐标转换矩阵
    private ArrayList mFacesRect = new ArrayList<RectF>();                                         //保存人脸坐标信息
    private FaceDetectListener mFaceDetectListener = null;                         //人脸检测回调

    private Handler mCameraHandler;
    private HandlerThread handlerThread = new HandlerThread("CameraThread");

    private Size mPreviewSize = new Size(PREVIEW_WIDTH, PREVIEW_HEIGHT);                      //预览大小
    private Size mSavePicSize = new Size(SAVE_WIDTH, SAVE_HEIGHT);                         //保存图片大小

    interface FaceDetectListener {
        public void onFaceDetect(Face[] faces, ArrayList<RectF> facesRect);
    }

    public void setFaceDetectListener(FaceDetectListener listener) {
        this.mFaceDetectListener = listener;
    }

    public CameraHelperFace(Activity mActivity, AutoFitTextureView textureView) {
        this.mActivity = mActivity;
        this.mTextureView = textureView;
        init();
    }


    public void init() {
        try {
            mDisplayRotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();  //手机方向

            handlerThread.start();
            mCameraHandler = new Handler(handlerThread.getLooper());

            mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
                @Override
                public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                    configureTransform(width, height);
                    try {
                        initCameraInfo();

                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }

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

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

                @Override
                public void onSurfaceTextureUpdated(SurfaceTexture surface) {

                }
            });

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 初始化
     */
    private void initCameraInfo() throws CameraAccessException {
        mCameraManager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
        String[] cameraIdList = mCameraManager.getCameraIdList();
        if (cameraIdList.length == 0) {

            MyToast.show(mActivity, "没有可用相机");
            return;
        }

        for (String id :cameraIdList) {
            CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(id);
            int facing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING);

            if (facing == mCameraFacing) {
                mCameraId = id;
                mCameraCharacteristics = cameraCharacteristics;
            }
            log("设备中的摄像头 $id");
        }



        int supportLevel = mCameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
        if (supportLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
            MyToast.show(mActivity,"相机硬件不支持新特性");
        }

        //获取摄像头方向
        mCameraSensorOrientation = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
        //获取StreamConfigurationMap,它是管理摄像头支持的所有输出格式和尺寸
        StreamConfigurationMap configurationMap = mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);

        Size[] savePicSize = configurationMap.getOutputSizes(ImageFormat.JPEG);          //保存照片尺寸
        Size[] previewSize = configurationMap.getOutputSizes(SurfaceTexture.class); //预览尺寸

        List<Size> savePicSizeList=new ArrayList<>();
        for(int i=0;i<savePicSize.length;i++){
            savePicSizeList.add(savePicSize[i]);
        }

        List<Size> previewSizeList=new ArrayList<>();
        for(int i=0;i<previewSize.length;i++){
            previewSizeList.add(previewSize[i]);
        }

        boolean exchange = exchangeWidthAndHeight(mDisplayRotation, mCameraSensorOrientation);

        int tWidth = 0, tHeight = 0;
        if (exchange) {
            tWidth = mSavePicSize.getHeight();
            tHeight = mSavePicSize.getWidth();
        } else {
            tWidth = mSavePicSize.getWidth();
            tHeight = mSavePicSize.getHeight();
        }

        if (savePicSizeList != null) {
            mSavePicSize = getBestSize(
                    tWidth,
                    tHeight,
                    tWidth,
                    tHeight,
                    savePicSizeList);
        }
        int mWidth = 0, mHeight = 0;
        int nWidth=0,nHeight=0;
        if (exchange) {
            mWidth = mPreviewSize.getHeight();
            mHeight = mPreviewSize.getWidth();
            nWidth=mTextureView.getHeight();
            nHeight=mTextureView.getWidth();
        } else {
            mWidth = mPreviewSize.getWidth();
            mHeight = mPreviewSize.getHeight();
        }

        if (previewSizeList != null) {
            mPreviewSize = getBestSize(
                    mWidth,
                    mHeight,
                    nWidth,
                    nHeight,
                    previewSizeList);
        }

        mTextureView.getSurfaceTexture().setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());

        log("预览最优尺寸 :${mPreviewSize.width} * ${mPreviewSize.height}, 比例  ${mPreviewSize.width.toFloat() / mPreviewSize.height}");
        log("保存图片最优尺寸 :${mSavePicSize.width} * ${mSavePicSize.height}, 比例  ${mSavePicSize.width.toFloat() / mSavePicSize.height}");

        //根据预览的尺寸大小调整TextureView的大小,保证画面不被拉伸
        int orientation = mActivity.getResources().getConfiguration().orientation;
        if (orientation == Configuration.ORIENTATION_LANDSCAPE){
            mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
        } else{
            mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
        }


        mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.JPEG, 1);

        mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader it) {
                Image image = it.acquireNextImage();
                ByteBuffer byteBuffer = image.getPlanes()[0].getBuffer();
                int byteArray = byteBuffer.remaining();
                byteBuffer.get(byteArray);
                it.close();

            }
        }, mCameraHandler);
        if (openFaceDetect){
            initFaceDetect();
        }
        openCamera();
    }


    /**
     * 初始化人脸检测相关信息
     */
    private void initFaceDetect() {

        int faceDetectCount = mCameraCharacteristics.get(CameraCharacteristics.STATISTICS_INFO_MAX_FACE_COUNT);    //同时检测到人脸的数量
        int[] faceDetectModes = mCameraCharacteristics.get(CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES);  //人脸检测的模式

        if (faceDetectModes != null) {
            mFaceDetectMode = CaptureRequest.STATISTICS_FACE_DETECT_MODE_FULL;
        }else{
            mFaceDetectMode = CaptureRequest.STATISTICS_FACE_DETECT_MODE_OFF;
        }

        if (mFaceDetectMode == CaptureRequest.STATISTICS_FACE_DETECT_MODE_OFF) {
            MyToast.show(mActivity, "相机硬件不支持人脸检测");
            return;
        }

        Rect activeArraySizeRect = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); //获取成像区域
        float scaledWidth = (float)mPreviewSize.getWidth() / (float)activeArraySizeRect.width();
        float scaledHeight = (float)mPreviewSize.getHeight() / (float)activeArraySizeRect.height();
        boolean mirror = mCameraFacing == CameraCharacteristics.LENS_FACING_FRONT;

        mFaceDetectMatrix.setRotate(mCameraSensorOrientation);
        if (mirror) {
            mFaceDetectMatrix.postScale(-scaledWidth, scaledHeight);
        } else {
            mFaceDetectMatrix.postScale(scaledWidth, scaledHeight);
        }
        if (exchangeWidthAndHeight(mDisplayRotation, mCameraSensorOrientation))
            mFaceDetectMatrix.postTranslate(mPreviewSize.getHeight(), mPreviewSize.getWidth());


        Log.v("camera","成像区域  "+activeArraySizeRect.width()+","+activeArraySizeRect.height());
        Log.v("camera","预览区域  "+mPreviewSize.getWidth()+","+ mPreviewSize.getHeight());


        Log.v("camera","同时检测到人脸的数量 "+faceDetectCount);
    }

    /**
     * 打开相机
     */
    private void openCamera() throws CameraAccessException {

        if (ContextCompat.checkSelfPermission(mActivity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            MyToast.show(mActivity, "没有相机权限!");
            return;
        }
        mCameraManager.openCamera(mCameraId, new CameraDevice.StateCallback() {
            @Override
            public void onOpened(@NonNull CameraDevice camera) {
                log("onOpened");
                mCameraDevice = camera;
                try {
                    createCaptureSession(camera);
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onDisconnected(@NonNull CameraDevice camera) {

            }

            @Override
            public void onError(@NonNull CameraDevice camera, int error) {
                MyToast.show(mActivity, "打开相机失败!$error");
            }
        }, mCameraHandler);


    }

    /**
     * 创建预览会话
     */
    private void createCaptureSession(CameraDevice cameraDevice) throws CameraAccessException {

        final CaptureRequest.Builder captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);

        Surface surface = new Surface(mTextureView.getSurfaceTexture());
        captureRequestBuilder.addTarget(surface);  // 将CaptureRequest的构建器与Surface对象绑定在一起
        captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);     // 闪光灯
        captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);// 自动对焦

        if (openFaceDetect && mFaceDetectMode != CaptureRequest.STATISTICS_FACE_DETECT_MODE_OFF)
            captureRequestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, CameraCharacteristics.STATISTICS_FACE_DETECT_MODE_SIMPLE);//人脸检测

        List<Surface> sList = new ArrayList<>();
        sList.add(surface);
        sList.add(mImageReader.getSurface());
        // 为相机预览,创建一个CameraCaptureSession对象
        cameraDevice.createCaptureSession(sList, new CameraCaptureSession.StateCallback() {
            @Override
            public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                MyToast.show(mActivity, "开启预览会话失败!");
            }

            @Override
            public void onConfigured(@NonNull CameraCaptureSession session) {
                mCameraCaptureSession = session;
                try {
                    session.setRepeatingRequest(captureRequestBuilder.build(), new CameraCaptureSession.CaptureCallback() {
                        @Override
                        public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                            super.onCaptureCompleted(session, request, result);
                            if (openFaceDetect && mFaceDetectMode != CaptureRequest.STATISTICS_FACE_DETECT_MODE_OFF)
                                handleFaces(result);
                            canExchangeCamera = true;
                            canTakePic = true;
                        }

                        @Override
                        public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {
                            super.onCaptureStarted(session, request, timestamp, frameNumber);

                        }

                        @Override
                        public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) {
                            super.onCaptureFailed(session, request, failure);
                            log("onCaptureFailed");
                            MyToast.show(mActivity, "开启预览失败!");
                        }
                    }, mCameraHandler);
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                }
            }
        }, mCameraHandler);


    }


    /**
     * 处理人脸信息
     */
    private void handleFaces(final TotalCaptureResult result) {
        Face[] faces = result.get(CaptureResult.STATISTICS_FACES);
        mFacesRect.clear();

        for (Face face : faces) {
//        if (faces.length > 0) {
//            Face face = faces[0];
            Rect bounds = face.getBounds();
            int left = bounds.left;
            int top = bounds.top;
            int right = bounds.right;
            int bottom = bounds.bottom;

            RectF rawFaceRect = new RectF(left, top, right, bottom);
            mFaceDetectMatrix.mapRect(rawFaceRect);
            RectF resultFaceRect = null;
            if (mCameraFacing == CaptureRequest.LENS_FACING_FRONT) {
                resultFaceRect = rawFaceRect;
            } else {
                resultFaceRect = new RectF(rawFaceRect.left, rawFaceRect.top - mPreviewSize.getWidth(), rawFaceRect.right, rawFaceRect.bottom - mPreviewSize.getWidth());
            }


            mFacesRect.add(resultFaceRect);

            log("原始人脸位置: ${bounds.width()} * ${bounds.height()}   ${bounds.left} ${bounds.top} ${bounds.right} ${bounds.bottom}   分数: ${face.score}");
            log("转换后人脸位置: ${resultFaceRect.width()} * ${resultFaceRect.height()}   ${resultFaceRect.left} ${resultFaceRect.top} ${resultFaceRect.right} ${resultFaceRect.bottom}   分数: ${face.score}");
        }
        mActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mFaceDetectListener.onFaceDetect(result.get(CaptureResult.STATISTICS_FACES), mFacesRect);
            }
        });

        Log.v("Camera","onCaptureCompleted  检测到 "+faces.length+"张人脸");
    }


    /**
     * 拍照
     */
    public void takePic() throws CameraAccessException {
        if (mCameraDevice == null || !mTextureView.isAvailable() || !canTakePic) return;


//                mCameraDevice.apply {
//
//            CaptureRequest.Builder captureRequestBuilder = createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
//            mImageReader.getSurface();
//            { captureRequestBuilder.addTarget(it) }
//
//            captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); // 自动对焦
//            captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH) ;    // 闪光灯
//            captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, mCameraSensorOrientation);      //根据摄像头方向对保存的照片进行旋转,使其为"自然方向"
//            mCameraCaptureSession.capture(captureRequestBuilder.build(),null,mCameraHandler);

//        }
    }

    /**
     * 切换摄像头
     */
    public void exchangeCamera() throws CameraAccessException {
        if (mCameraDevice == null || !canExchangeCamera || !mTextureView.isAvailable()) {
            return;
        }
        if (mCameraFacing == CameraCharacteristics.LENS_FACING_FRONT) {
            mCameraFacing = CameraCharacteristics.LENS_FACING_BACK;
            mCameraId="0";
        } else {
            mCameraFacing = CameraCharacteristics.LENS_FACING_FRONT;
            mCameraId="1";
        }
        mPreviewSize = new Size(PREVIEW_WIDTH, PREVIEW_HEIGHT); //重置预览大小

        releaseCamera();
        initCameraInfo();
    }

    /**
     * 根据提供的参数值返回与指定宽高相等或最接近的尺寸
     *
     * @param targetWidth  目标宽度
     * @param targetHeight 目标高度
     * @param maxWidth     最大宽度(即TextureView的宽度)
     * @param maxHeight    最大高度(即TextureView的高度)
     * @param sizeList     支持的Size列表
     * @return 返回与指定宽高相等或最接近的尺寸
     */
    private Size getBestSize(int targetWidth, int targetHeight, int maxWidth, int maxHeight, List<Size> sizeList) {
        List<Size> bigEnough = new ArrayList<Size>();     //比指定宽高大的Size列表
        List<Size> notBigEnough = new ArrayList<Size>();  //比指定宽高小的Size列表

        for (Size size : sizeList) {

            //宽<=最大宽度  &&  高<=最大高度  &&  宽高比 == 目标值宽高比
            if (size.getWidth() <= maxWidth && size.getHeight() <= maxHeight
                    && size.getWidth() == size.getHeight() * targetWidth / targetHeight) {

                if (size.getWidth() >= targetWidth && size.getHeight() >= targetHeight)
                    bigEnough.add(size);
                else
                    notBigEnough.add(size);
            }
            log("系统支持的尺寸: ${size.width} * ${size.height} ,  比例 :${size.width.toFloat() / size.height}");
        }

        log("最大尺寸 :$maxWidth * $maxHeight, 比例 :${targetWidth.toFloat() / targetHeight}");
        log("目标尺寸 :$targetWidth * $targetHeight, 比例 :${targetWidth.toFloat() / targetHeight}");

        //选择bigEnough中最小的值  或 notBigEnough中最大的值
        if (bigEnough.size() > 0) {
            return Collections.min(bigEnough, new CameraHelperFace.CompareSizesByArea());
        } else if (notBigEnough.size() > 0) {
            return Collections.max(notBigEnough, new CameraHelperFace.CompareSizesByArea());
        } else {
            return sizeList.get(0);
        }
    }

    /**
     * 根据提供的屏幕方向 [displayRotation] 和相机方向 [sensorOrientation] 返回是否需要交换宽高
     */
    /**
     * 根据提供的屏幕方向 [displayRotation] 和相机方向 [sensorOrientation] 返回是否需要交换宽高
     */
    private boolean exchangeWidthAndHeight(int displayRotation, int sensorOrientation) {
        boolean exchange = false;
        switch (displayRotation) {
            case Surface.ROTATION_0:
            case Surface.ROTATION_180:
                if (sensorOrientation == 90 || sensorOrientation == 270) {
                    exchange = true;
                }
                break;
            case Surface.ROTATION_90:
            case Surface.ROTATION_270:
                if (sensorOrientation == 0 || sensorOrientation == 180) {
                    exchange = true;
                }
                break;
            default:
                log("Display rotation is invalid: $displayRotation");
                break;
        }

        log("屏幕方向  $displayRotation");
        log("相机方向  $sensorOrientation");
        return exchange;
    }


    public void releaseCamera() {
        if(mCameraCaptureSession!=null){
            mCameraCaptureSession.close();
            mCameraCaptureSession = null;
        }
        if(mCameraDevice!=null) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
        if(mImageReader!=null) {
            mImageReader.close();
            mImageReader = null;
        }
        canExchangeCamera = false;
    }

    public void releaseThread() throws InterruptedException {
        handlerThread.quitSafely();
        handlerThread.join();
    }


    /**
     * Configures the necessary [android.graphics.Matrix] transformation to `mTextureView`.
     * This method should be called after the camera preview size is determined in
     * setUpCameraOutputs and also the size of `mTextureView` is fixed.
     *
     * @param viewWidth  The width of `mTextureView`
     * @param viewHeight The height of `mTextureView`
     */
    private void configureTransform(int viewWidth, int viewHeight) {
        int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
        Matrix matrix = new Matrix();
        RectF viewRect = new RectF(0f, 0f, viewWidth, viewHeight);
        RectF bufferRect = new RectF(0f, 0f, mPreviewSize.getHeight(), mPreviewSize.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);
            int scale = Math.max(
                    viewHeight / mPreviewSize.getHeight(),
                    viewWidth / mPreviewSize.getWidth());
            matrix.postScale(scale, scale, centerX, centerY);
            matrix.postRotate((90 * (rotation - 2)), centerX, centerY);
        } else if (Surface.ROTATION_180 == rotation) {
            matrix.postRotate(180f, centerX, centerY);
        }
        mTextureView.setTransform(matrix);
    }

    public class CompareSizesByArea implements Comparator<Size> {

        @Override
        public int compare(Size size1, Size size2) {
            return java.lang.Long.signum((long)(size1.getWidth() * size1.getHeight() - size2.getWidth() * size2.getHeight()));
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值