IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android 原生的人脸识别Camera+FaceDetector示例 -> 正文阅读

[移动开发]Android 原生的人脸识别Camera+FaceDetector示例

人脸识别google有自带api,Androoid 原生的人脸识别是利用相机的摄像功能和android.media.FaceDetector这个类来实现的

1、android.hardware.camera

1.1 简介

Camera 类用于设置图像捕获设置、开始/停止预览、抓拍图片和检索帧以进行视频编码
Camera的源码

1.2 人脸识别步骤:

1. 初始化相机并设置相机参数;
2. 设置预览监听setPreviewDisplay();
3. 开始预览startPreview();
4. 实现预览的接口onPreviewFrame并处理每一帧的数据转成Bitmap;
5. 通过 faceDetector.findFaces()发现人脸,注意:faceDetector只能识别Bitmap.Config.RGB_565的Bitmap

1.3 示例类

工具类CameraUtils

 package com.zw.camera2test.camera;


import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.media.MediaRecorder;
import android.os.SystemClock;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;


public class CameraUtils implements Camera.PreviewCallback {
    private static final String TAG = "Camera2Utils";
    private SurfaceView mSurfaceView;
    private FaceRectView mFaceRectView;
    private static CameraUtils mCameraUtils;
    private SurfaceHolder mSurfaceViewHolder;
    private int cameraPosition = 1; //0 后置    1 前置
    private Camera mCamera;
    private int CAMERA_WIDHT = 640;
    private int CAMERA_HEIGHT = 480;

    //    private int CAMERA_WIDHT = 320;
//    private int CAMERA_HEIGHT = 240;
    private boolean detectFace = false;
    private MediaRecorder mediaRecorder;
    /***录制视频的videoSize*/
    private int height = 480, width = 640;
    /***保存的photo的height ,width*/
    private int heightPhoto = 480, widthPhoto = 640;

    private CameraErrorCallback cameraErrorCallback;

    public static CameraUtils getInstance() {
        if (mCameraUtils == null) {
            synchronized (CameraUtils.class) {
                if (mCameraUtils == null) {
                    mCameraUtils = new CameraUtils();
                }
            }
        }
        return mCameraUtils;
    }

    public void initCamera(SurfaceView surfaceView, FaceRectView faceRectView) {
        this.mSurfaceView = surfaceView;
        this.mFaceRectView = faceRectView;
        mSurfaceViewHolder = mSurfaceView.getHolder();
        mSurfaceViewHolder.setFormat(PixelFormat.OPAQUE);
        surfaceHolderCB = new SurfaceHolderCB();
    }

    SurfaceHolderCB surfaceHolderCB;

    public void startCamera() {
        if (mSurfaceViewHolder != null) {
            mSurfaceViewHolder.addCallback(surfaceHolderCB);
        }
    }


    public void stopCamera() {
        stopPreview();
    }

    //打开摄像头失败
    public static final int ERROR_CAMERA_OPEN = 100001;

    public void startPreview() {
        Log.i(TAG, "--11111---2-----" + mCamera);
        if (mCamera != null) {
            stopCamera();
        }
        if (mCamera == null) {
            Log.i(TAG, "startPreview: " + cameraPosition);
            try {
                mCamera = Camera.open(cameraPosition);
                Log.i(TAG, "startPreview:------ " + (Camera.getNumberOfCameras() - 1));
            } catch (RuntimeException e) {
                Log.i(TAG, "startPreview: 方法有问题");
                if (cameraErrorCallback != null) {
                    cameraErrorCallback.onCameraError(ERROR_CAMERA_OPEN);
                }
                return;
            }

            Camera.Parameters parameters = setParameters(mCamera, cameraPosition);
            if (parameters.getMaxNumDetectedFaces() > 0) {
                Log.e("tag", "【FaceDetectorActivity】类的方法:【startFaceDetection】: " + parameters.getMaxNumDetectedFaces());
            } else {
                Log.e("tag", "【FaceDetectorActivity】类的方法:【startFaceDetection】: " + "不支持");
            }
            mCamera.setDisplayOrientation(0);
            mCamera.setErrorCallback(cameraErrorCallback);
            try {
                if (cameraPosition == 0) {
                    parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); //后置必须聚焦设置
                }
                mCamera.setPreviewDisplay(mSurfaceViewHolder);
                mCamera.setParameters(parameters);
                if (mCamera != null) {
                    mCamera.setPreviewCallback(this);
                }
            } catch (IOException e) {
                Log.i(TAG, "startPreview: " + e.getMessage());
                Camera.Parameters parameters1 = mCamera.getParameters();// 得到摄像头的参数
                mCamera.setParameters(parameters1);
            }

            mCamera.startPreview();
            mCamera.cancelAutoFocus();//聚焦
            detectFace = true;


        }


    }

    /**
     * 脸部检测接口
     */
    private class FaceDetectorListener implements Camera.FaceDetectionListener {
        @Override
        public void onFaceDetection(Camera.Face[] faces, Camera camera) {
            if (faces.length > 0) {
                int score = faces[0].score;
                Log.i(TAG, "onFaceDetection: score " + score);
                detectFace = true;
                mFaceRectView.drawFaceRects(faces, mSurfaceView, cameraPosition);
            } else {
                Log.i(TAG, "onFaceDetection: 没有人脸 ");
                detectFace = false;
                mFaceRectView.clearRect();
            }
        }
    }

    public void setDetectFace(boolean detectFace) {
        this.detectFace = detectFace;
    }

    /**
     * 切换前后相机
     */
    public void changeCamera(CurrentCameraPositionInterface cameraPositionInterface) {
        int numberOfCameras = Camera.getNumberOfCameras();
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        if (numberOfCameras >= 2) {
            if (cameraPosition == 0) { //现在为后置,变成为前置
                Camera.getCameraInfo(1, cameraInfo);
                if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { //CAMERA_FACING_FRONT 前置方位  CAMERA_FACING_BACK 后置方位
                    if (mCamera != null) {
                        stopPreview();
                    }
                    cameraPosition = 1;
                    startPreview();
                }
            } else if (cameraPosition == 1) {//前置更改为后置相机
                Camera.getCameraInfo(0, cameraInfo);
                if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                    if (mCamera != null) {
                        stopPreview();
                    }
                    cameraPosition = 0;
                    startPreview();
                }
            }
        }
        cameraPositionInterface.cameraPosition(cameraPosition);
    }


    private class SurfaceHolderCB implements SurfaceHolder.Callback {
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            Log.i(TAG, "--11111--------" + holder);
            startPreview();
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

            Log.i(TAG, "--2222222--------" + holder);
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            holder.removeCallback(this);
            stopPreview();
        }
    }

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        if (detectFace) {
            Bitmap bmp = byte2bitmap(data, camera);
            mCurrentDetectFaceInterface.detectFace(bmp);
            mCurrentDetectFaceInterface.detectFace2(bmp);
        }
    }

    private Bitmap byte2bitmap(byte[] bytes, Camera camera) {
        Bitmap bitmap = null;

        Camera.Size size = camera.getParameters().getPreviewSize(); // 获取预览大小
        final int w = size.width; // 宽度
        final int h = size.height;
        final YuvImage image = new YuvImage(bytes, ImageFormat.NV21, w, h,
                null);
        ByteArrayOutputStream os = new ByteArrayOutputStream(bytes.length);
        if (!image.compressToJpeg(new Rect(0, 0, w, h), 100, os)) {
            return null;
        }
        byte[] tmp = os.toByteArray();
        bitmap = BitmapFactory.decodeByteArray(tmp, 0, tmp.length);

        Matrix matrix = new Matrix();
//        matrix.setRotate(-90);
        Log.i(TAG, "byte2bitmap: " + bitmap.getWidth() + "---" + bitmap.getHeight());
        bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        return bitmap;
    }

    private void stopPreview() {
        if (mCamera != null) {
            try {
                mCamera.setPreviewCallback(null);
                mCamera.setPreviewDisplay(null);
                mCamera.stopPreview();
                mCamera.release();
                mCamera = null;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        detectFace = false;
        mFaceRectView.clearRect();
    }

    /**
     * 开启闪光灯
     */
    public void turnFlash() {
        try {
            if (mCamera == null || mCamera.getParameters() == null
                    || mCamera.getParameters().getSupportedFlashModes() == null) {
                return;
            }
            Camera.Parameters parameters = mCamera.getParameters();
            String mode = parameters.getFlashMode();
            if (Camera.Parameters.FLASH_MODE_OFF.equals(mode)) {
                parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
            } else if (Camera.Parameters.FLASH_MODE_TORCH.equals(mode)) {
                parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
            }
            mCamera.setParameters(parameters);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 调节预览的焦距
     *
     * @param maxValue
     * @param currentValue
     */
    public void changZoom(int maxValue, int currentValue) {
        if (mCamera != null) {
            Camera.Parameters parameters = mCamera.getParameters();
            int maxZoom = parameters.getMaxZoom();
            Log.i(TAG, "changZoom: " + maxZoom);
            float setZoom = ((float) maxZoom / maxValue) * currentValue;
            parameters.setZoom((int) setZoom);
            mCamera.setParameters(parameters);
        }
    }

    /**
     * @param path 保存的路径
     * @param name 录像视频名称(包含后缀)
     */
    public void startRecord(String path, String name) {
        if (mCamera == null)
            return;
        //解锁摄像头并将其分配给MediaRecorder
        mCamera.unlock();
        mediaRecorder = new MediaRecorder();
        mediaRecorder.setCamera(mCamera);

        //指定用于录制的输入源
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
        //mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_LOW));
        //设置配置文件,或者定义输出格式,音频视频编码器,帧速以及输出尺寸
        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
        // 设置录制的视频编码h263 h264
        mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        // 设置录制的视频帧率。必须放在设置编码和格式的后面,否则报错
        mediaRecorder.setVideoEncodingBitRate(700 * 1024);
//        设置视频录制的分辨率。必须放在设置编码和格式的后面,否则报错
        mediaRecorder.setVideoSize(width, height);
//        mediaRecorder.setVideoFrameRate(24);  //容易报错 还有上面的setVideoSize 都是需要底层支持
        File file = new File(path);
        if (!file.exists()) {
            file.mkdirs();
        }
        //指定一个输出文件
        mediaRecorder.setOutputFile(path + File.separator + name);
        File file1 = new File(path + File.separator + name);
        if (file1.exists()) {
            file1.delete();
        }
        //预览视频流,在指定了录制源和输出文件后,在prepare前设置
        mediaRecorder.setPreviewDisplay(mSurfaceView.getHolder().getSurface());

        /***不设置时,录制的视频总是倒着,翻屏导致视频上下翻滚*/
        if (cameraPosition == 1) {//前置相机
            mediaRecorder.setOrientationHint(180);
        } else if (cameraPosition == 0) {
            mediaRecorder.setOrientationHint(0);
        }

        try {
            //准备录制
            mediaRecorder.prepare();
            mediaRecorder.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 停止录制
     */
    public void stopRecord() {
        mediaRecorder.release();
        mCamera.release();
        mediaRecorder = null;
        SystemClock.sleep(200);
        if (mCamera != null) {
            mCamera = Camera.open();
            mediaRecorder = new MediaRecorder();
            doChange(mSurfaceView.getHolder());
        }

    }

    /**
     * 拍照使用
     *
     * @param photoPath
     * @param photoName
     */
    public void takePicture(String photoPath, String photoName, TakePictureSuccess mTakePictureSuccess) {
        File file = new File(photoPath);
        if (!file.exists()) {
            file.mkdir();
        }
        Camera.ShutterCallback shutter = new Camera.ShutterCallback() {
            @Override
            public void onShutter() {

            }
        };
        Camera.Parameters parameters = mCamera.getParameters();
        parameters.setPictureFormat(ImageFormat.JPEG);//图片格式 必设置  否则无法获取图片信息
        parameters.set("jpeg-quality", 90);//设置图片的质量
        mCamera.setParameters(parameters);
        mCamera.takePicture(shutter, null, new PictureCallBack(photoPath, photoName, mTakePictureSuccess));
//        mCamera.takePicture(null, null, new PictureCallBack(photoPath, photoName));
    }

    /*** 拍照功能*/
    private class PictureCallBack implements Camera.PictureCallback {
        /*** 照片保存的路径和名称*/
        private String path;
        private String name;
        private TakePictureSuccess mTakePictureSuccess;

        public PictureCallBack(String path, String name, TakePictureSuccess mTakePictureSuccess) {
            this.path = path;
            this.name = name;
            this.mTakePictureSuccess = mTakePictureSuccess;
        }

        @Override
        public void onPictureTaken(byte[] bytes, Camera camera) {
            File file = new File(path, name);
            if (file.exists()) {
                file.delete();
            }
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(file);
                try {
                    fos.write(bytes);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
            Log.i(TAG, "onPictureTaken: ----" + cameraPosition);
            Matrix matrix = new Matrix();
            if (cameraPosition == 0) {
                matrix.postRotate(90);
            } else {
                matrix.postRotate(270);
//                matrix.postRotate(180);
                matrix.postTranslate(-1, 0);
            }
            bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
            mTakePictureSuccess.takePictureSuccess(bitmap);
            //拍照结束 继续预览
            camera.startPreview();
        }
    }

    public interface TakePictureSuccess {
        void takePictureSuccess(Bitmap bitmap);
    }

    /**
     * 切换时,重新开启
     *
     * @param holder
     */
    private void doChange(SurfaceHolder holder) {
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.setDisplayOrientation(90);
            mCamera.startPreview();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 设置相机参数
     *
     * @param camera
     * @param cameraPosition
     * @return
     */
    public Camera.Parameters setParameters(Camera camera, int cameraPosition) {
        Camera.Parameters parameters = null;
        if (camera != null) {
            parameters = camera.getParameters();
            Log.i(TAG, "setParameters: ----" + parameters.getMaxNumDetectedFaces());
            if (cameraPosition == 0) {
                parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
                parameters.setPreviewFrameRate(25);
            }
            parameters.setPictureFormat(ImageFormat.NV21);

            //手机支持的预览尺寸集合
            List<Camera.Size> sizeList = parameters.getSupportedPreviewSizes();
            Log.i(TAG, "setParameters:----111----- " + CAMERA_WIDHT);
            Log.i(TAG, "setParameters:----支持预览----- " + sizeList.size());
            CAMERA_WIDHT = 640;
            CAMERA_HEIGHT = 480;
          /*  if (sizeList.size() > 1) {
                Iterator<Camera.Size> itor = sizeList.iterator();
                while (itor.hasNext()) {
                    Camera.Size cur = itor.next();
                    Log.i(TAG, "setParameters:----size----- "+cur.width +"-----"+cur.height) ;
                    if (cur.width >= CAMERA_WIDHT
                            && cur.height >= CAMERA_HEIGHT) {
                        CAMERA_WIDHT = cur.width;
                        CAMERA_HEIGHT = cur.height;
                        break;
                    }
                }
            }*/
            //每秒3帧  每秒从摄像头里面获得3个画面
//            parameters.setPreviewFrameRate(3);
            //设置拍出来的屏幕大小
            parameters.setPictureSize(CAMERA_WIDHT, CAMERA_HEIGHT);
            //获得摄像区域的大小
            parameters.setPreviewSize(CAMERA_WIDHT, CAMERA_HEIGHT);
            //对焦模式设置
            List<String> supportedFocusModes = parameters.getSupportedFocusModes();
            if (supportedFocusModes != null && supportedFocusModes.size() > 0) {
                if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
                    parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
                } else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
                    parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
                } else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
                    parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
                }
            }
        }


        return parameters;
    }

    /***
     * 获取SupportedVideoSizes 控制输出视频width在300到600之间(尽可能小)
     * 获取PictureSize的大小(控制在w:1000-2000之间)
     */
    public void getVideoSize() {
        if (mCamera == null)
            return;
        Camera.Parameters parameters = mCamera.getParameters();
        List<Camera.Size> videoSize = parameters.getSupportedVideoSizes();
        for (int i = 0; i < videoSize.size(); i++) {
            int width1 = videoSize.get(i).width;
            int height1 = videoSize.get(i).height;
            if (width1 >= 300 && width1 <= 600) {
                if (height1 >= 200 && height1 <= 600) {
                    width = width1;
                    height = height1;
                }
            }
//            Log.d(TAG, "getVideoSize:----w:-- " + videoSize.get(i).width + "---h:--" + videoSize.get(i).height);
            Log.d(TAG, "width " + width + "---height" + height);
        }
        List<Camera.Size> photoSize = parameters.getSupportedPictureSizes();
        for (int i = 0; i < photoSize.size(); i++) {
            int width1 = photoSize.get(i).width;
            int height1 = photoSize.get(i).height;
            if (width1 >= 1000 && width1 <= 2000) {
                if (height1 >= 600 && height1 <= 2000) {
                    widthPhoto = width1;
                    heightPhoto = height1;
                }
            }
        }
        Log.i(TAG, "getVideoSize: " + widthPhoto + "----" + heightPhoto);
    }

    public interface CurrentCameraPositionInterface {
        void cameraPosition(int cameraPosition);
    }

    public void setCurrentDetectFaceInterface(CurrentDetectFaceInterface currentDetectFaceInterface) {
        mCurrentDetectFaceInterface = currentDetectFaceInterface;
    }

    private CurrentDetectFaceInterface mCurrentDetectFaceInterface;

    public interface CurrentDetectFaceInterface {
        void detectFace(Bitmap bitmap);

        void detectFace2(Bitmap bitmap);
    }

    public void setCameraErrorCallback(CameraErrorCallback cameraErrorCallback) {
        this.cameraErrorCallback = cameraErrorCallback;
    }
}

发现人脸的方法

public Bitmap getCutBitmap(Bitmap bitmap) {
        Bitmap cutBitmap = null;
        //由于Android内存有限,图片太大的话,会出现无法加载图片的异常,图片的格式必须为Bitmap RGB565格式
        Bitmap bitmapDetect = bitmap.copy(Bitmap.Config.RGB_565, true);
        Log.i(TAG, "getCutBitmap:---宽度-- "+bitmapDetect.getWidth());
        Log.i(TAG, "getCutBitmap:---高度--  "+bitmapDetect.getHeight());
        // 设置你想检测的数量,数值越大错误率越高,所以需要置信度来判断,但有时候置信度也会出问题
        FaceDetector faceDetector = new FaceDetector(bitmapDetect.getWidth(), bitmapDetect.getHeight(), 1);
        // 返回找到图片中人脸的数量,同时把返回的脸部位置信息放到faceArray中,过程耗时,图片越大耗时越久
        int face = faceDetector.findFaces(bitmapDetect, faces);
        Log.i(TAG, "getCutBitmap:--------- "+face);
//        if (faces[0] != null) {//检测到人脸
        if (face > 0) {//检测到人脸
            // 获取传回的第一张脸信息
            FaceDetector.Face face1 = faces[0];
            // 获取该部位为人脸的可信度,0~1
            float confidence = face1.confidence();
            Log.i(TAG, "------人脸可信度---- "+confidence);
            // 获取双眼的间距
            float eyesDistance = face1.eyesDistance();
            Log.i(TAG, "------人脸双眼的间距---- "+eyesDistance);
            // 传入X则获取到x方向上的角度,传入Y则获取到y方向上的角度,传入Z则获取到z方向上的角度
            float angle = face1.pose(FaceDetector.Face.EULER_X);
            if (confidence < 0.5 || eyesDistance<0.3){
                return null;
            }
            cutBitmap = bitmap;
            Log.i(TAG, "getCutBitmap: ");
        }
        bitmapDetect.recycle();
//        bitmap.recycle();
        return cutBitmap;
    }

2 . Demo地址

下载地址

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-05-08 08:15:26  更:2022-05-08 08:16:08 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 1:07:14-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码