android 相机人脸检测 人脸识别 画人脸边框 识别成功保存图片 mlkit 机器学习

ML Kit 是一款移动 SDK,可将 Google 的设备端机器学习专业知识融入到 Android 和 iOS 应用中。使用我们功能强大且易于使用的生成式 AI、Vision 和 Natural Language API 来解决应用中的常见难题,或打造全新的用户体验。所有功能均由 Google 的一流机器学习模型提供支持,并且免费提供给您。

随着移动端计算性能的增强,实时人脸检测与表情识别的需求日益增加,如社交应用的趣味滤镜、视频会议中的人脸跟踪、以及安防领域的面部分析等。本文章通过结合 CameraX 和 ML Kit,展示了如何在Android上快速实现高效、稳定的人脸检测与表情识别功能。

项目亮点:

1 实时检测人脸特征:包括微笑指数、眼睛睁开状态等关键信息。

2 直观的视觉化界面:自定义绘制层显示检测的轮廓点、表情符号和辅助信息。

3 现代化架构:基于CameraX,摆脱传统复杂的Camera API,兼顾易用性与性能。

4 高度可扩展性:可以轻松拓展到AR特效、行为分析等高级应用场景。

1. CameraX简介

1.1 CameraX介绍

Q1: 什么是 CameraX?

CameraX 是 Google 提供的 Android 相机库,旨在简化 Android 开发中相机功能的实现,并提供更好的跨设备兼容性。它通过统一的 API 接口来简化底层硬件的管理,使得开发者能够轻松地在不同的 Android 设备上实现相机相关的功能,而不需要处理复杂的设备差异。

Q2: CameraX 的工作原理是什么?

CameraX 的核心是相机功能的封装:它提供了几个模块来处理常见的相机操作:

Preview:展示实时的相机预览。

ImageCapture:拍摄照片。

ImageAnalysis:对图像流进行实时分析,用于人脸识别、物体检测等应用。

VideoCapture:录制视频。

这些模块通过统一的 API 接口进行操作。开发者通过 CameraX 可以轻松访问相机硬件的功能,而无需关心设备差异。CameraX 自动处理设备的适配工作,确保应用能够在各种 Android 设备上正常运行。

Q3: CameraX 一般用于哪里?有什么优缺点?

适用场景:

相机预览:展示实时摄像头画面,例如视频聊天、相机应用中的实时预览。

图像分析:进行人脸检测、物体识别、条码扫描等实时图像处理。

拍照与视频录制:在应用中实现拍照、录像功能。

优点:

简化开发:CameraX 提供了易于使用的 API 接口,开发者可以快速实现相机功能。

跨设备兼容性:CameraX 自动适配不同型号的设备,减少了设备差异带来的问题。

与 Jetpack 集成:与 Android Jetpack 库紧密集成,支持生命周期管理,提升了开发效率。

图像分析支持:通过 ImageAnalysis 模块,CameraX 可以与 ML Kit 等机器学习工具结合,进行实时图像分析。

缺点:

硬件限制:虽然 CameraX 提供了广泛的兼容性,但仍可能受设备硬件性能和系统限制的影响,特别是在低端设备上。

功能相对简单:尽管 CameraX 提供了很多基本的相机功能,但对于一些复杂的相机操作(如深度摄像、手动对焦等)可能不如传统相机 API 灵活。

2. ML Kit简介

2.1 ML Kit介绍

Q1: 什么是 ML Kit?

ML Kit 是 Google 提供的一个跨平台的机器学习工具包,旨在简化 Android 和 iOS 应用中机器学习功能的集成。它提供了一些现成的 API,帮助开发者在应用中快速实现各种机器学习任务,如图像分析、文字识别、语言处理等,无需深入了解机器学习的细节。

Q2: ML Kit 的工作原理是什么?

ML Kit 的核心是通过集成各种机器学习模型和预训练的 API,简化开发者的工作。开发者只需要调用相应的 API 接口,提供输入数据(如图像、文字、声音等),ML Kit 会自动进行处理并返回结果。ML Kit 支持两种主要的工作模式:

基于云端的 ML(Cloud-based):

ML Kit 使用 Google Cloud 的机器学习模型来处理数据,适用于需要高计算能力或大规模数据集的任务。例如,文字识别、面部识别等任务,处理速度和准确度较高,但需要网络连接。

基于设备的 ML(On-device):

ML Kit 使用本地设备的硬件资源来处理数据,无需互联网连接。它通过优化后的轻量级模型进行计算,适用于低延迟、隐私敏感的任务。例如,面部检测、条码扫描、语言翻译等任务。

Q3: ML Kit 一般用于哪里?有什么优缺点?

适用场景:

文字识别:将图像中的文字转换为文本,例如在文档扫描、图片文字识别中使用。

条码识别:快速扫描条形码和二维码,广泛应用于购物、票务等场景。

人脸检测:分析图像中的人脸,识别面部特征,用于人脸识别、面部表情分析等应用。

图像标记:识别图像中的常见对象,如动物、植物、物品等,用于图像分类、物体识别等。

语言翻译:通过机器学习技术对不同语言的文本进行实时翻译。

姿势检测:识别人体的关键姿势,用于运动、健身、游戏等场景。

优点:

易于使用:ML Kit 提供简单的 API 接口,开发者可以快速集成各种机器学习功能,而无需深入了解机器学习的技术细节。

预训练模型:ML Kit 提供的许多功能已经包含了预训练好的机器学习模型,减少了开发和训练模型的时间和成本。

跨平台支持:ML Kit 不仅支持 Android 平台,也支持 iOS,开发者可以在不同平台之间共享机器学习代码。

本地化支持:部分功能(如人脸检测、条码识别等)支持本地计算,无需网络连接,适合离线使用。

高效性:基于设备的模型使用本地硬件计算,具有较低的延迟和较好的性能。

项目效果图

:有人脸边框 ,眼睛 鼻子 嘴巴 眉毛

1 。导入依赖包

java 复制代码
        implementation 'androidx.constraintlayout:constraintlayout:2.1.4'

        // ML Kit 人脸检测
        implementation 'com.google.mlkit:face-detection:16.1.5'

//        // CameraX
        def camerax_version = "1.3.1"
        implementation "androidx.camera:camera-core:${camerax_version}"
        implementation "androidx.camera:camera-camera2:${camerax_version}"
        implementation "androidx.camera:camera-lifecycle:${camerax_version}"
        implementation "androidx.camera:camera-view:${camerax_version}"

2 。AndroidManifest.xml 添加权限 和配置Activity

java 复制代码
    <!-- 摄像头权限 -->
    <uses-permission android:name="android.permission.CAMERA" />
    <!-- 声明使用摄像头特性 -->
    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />

        <activity
            android:name=" .RenLianActivity"
            android:screenOrientation="portrait"
            android:theme="@style/NoActionBarCustoms" />

3.RenLianActivity

java 复制代码
package com.sbas.mybledemohk;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Gravity;
import android.view.Surface;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.AspectRatio;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.ImageProxy;
import androidx.camera.core.Preview;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.PreviewView;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.google.android.material.button.MaterialButton;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.mlkit.vision.common.InputImage;
import com.google.mlkit.vision.face.Face;
import com.google.mlkit.vision.face.FaceDetection;
import com.google.mlkit.vision.face.FaceDetector;
import com.google.mlkit.vision.face.FaceDetectorOptions;
import com.google.mlkit.vision.face.FaceContour;
import com.google.mlkit.vision.face.FaceLandmark;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import android.annotation.SuppressLint;

@SuppressWarnings("deprecation")
@SuppressLint("UnsafeOptInUsageError")
public class RenLianActivity extends AppCompatActivity {


    private Handler  handler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            takePhoto();
        }
    };

    private static final String TAG = "MainActivity";
    private static final int REQUEST_CODE_PERMISSIONS = 10;
    private static final String[] REQUIRED_PERMISSIONS = new String[]{Manifest.permission.CAMERA};

    private PreviewView viewFinder;
    private ExecutorService cameraExecutor;
    private FaceDetector faceDetector;
    private FaceOverlayView faceOverlayView;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);

        viewFinder = findViewById(R.id.viewFinder);
        faceOverlayView = findViewById(R.id.faceOverlay);

        // 检查权限
        if (allPermissionsGranted()) {
            startCamera();
        } else {
            ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
        }

        // 设置人脸检测器 它用于处理从相机获取的图像并检测人脸。使用FaceDetectorOptions来设置检  测的性能模式、轮廓模式和分类模式。
        FaceDetectorOptions options = new FaceDetectorOptions.Builder()
                .setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST)
                .setContourMode(FaceDetectorOptions.CONTOUR_MODE_ALL)
                .setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
                .build();
        faceDetector = FaceDetection.getClient(options);

        cameraExecutor = Executors.newSingleThreadExecutor();
    }
    ImageCapture imageCapture;
    private void startCamera() {
        ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this);

        cameraProviderFuture.addListener(() -> {
            try {
            
                ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
       //在此方法中,使用ProcessCameraProvider来绑定前置相机(CameraSelector.DEFAULT_FRONT_CAMERA)并设置预览、图像分析等。 
//Preview:用于显示相机的预览视图。
//ImageAnalysis:处理每一帧图像,进行人脸分析。
                Preview preview = new Preview.Builder()
                        .setTargetRotation(Surface.ROTATION_0)
                        .build();

                preview.setSurfaceProvider(viewFinder.getSurfaceProvider());
                viewFinder.post(() -> {
                    faceOverlayView.setPreviewSize(
                            viewFinder.getWidth(),
                            viewFinder.getHeight()
                    );
                });
                //拍照   使用这个类  。
                imageCapture = new ImageCapture.Builder().build();
                ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
                        .setTargetRotation(Surface.ROTATION_0)
                        .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                        .build();

                imageAnalysis.setAnalyzer(cameraExecutor, this::analyzeFace);

                CameraSelector cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA;

                cameraProvider.unbindAll();
                cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis,imageCapture);

            } catch (ExecutionException | InterruptedException e) {
                Log.e(TAG, "相机启动失败", e);
            }
        }, ContextCompat.getMainExecutor(this));
    }
    private  void takePhoto() {
        if(imageCapture == null){
            Toast.makeText(this,"拍照异常",Toast.LENGTH_SHORT).show();
            return;
        }
        File file = new File(getExternalFilesDir(null), System.currentTimeMillis() + ".jpg");
        ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(file).build();
        Toast.makeText(this,file.getAbsolutePath().toString(),Toast.LENGTH_SHORT).show();
        imageCapture.takePicture(outputFileOptions, cameraExecutor, new ImageCapture.OnImageSavedCallback() {
            @Override
            public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
                Uri uri = outputFileResults.getSavedUri();
                if(uri != null){
                    File absoluteFile = file.getAbsoluteFile();
                    Log.i(TAG, "onImageSaved: absoluteFile:" + absoluteFile);
                }

            }
            @Override
            public void onError(@NonNull ImageCaptureException exception) {
                Log.i(TAG, "onError: " + exception.getMessage(),exception);
            }
        });
    }

    private void  UploadImg(File file )  {

    }
    long  last_time  = 0;
    @SuppressWarnings("deprecation")
    private void analyzeFace(@NonNull ImageProxy image) {
        // 更新图像信息
        faceOverlayView.setImageSourceInfo(
                image.getWidth(),
                image.getHeight(),
                image.getImageInfo().getRotationDegrees()
        );
//在analyzeFace()方法中,通过InputImage.fromMediaImage()将ImageProxy转换为InputImage,然后调用faceDetector.process(inputImage)进行人脸检测。
        InputImage inputImage = InputImage.fromMediaImage(
                image.getImage(),
                image.getImageInfo().getRotationDegrees()
        );

        faceDetector.process(inputImage)
                .addOnSuccessListener(faces -> {
                    runOnUiThread(() -> {
                        if((System.currentTimeMillis() - last_time)>150000) {
                            for (Face face : faces) {
                                for (FaceContour contour : face.getAllContours()) {
                                    if (contour.getFaceContourType() == FaceContour.LOWER_LIP_BOTTOM) {  //识别到人脸自动拍照  ,间隔15S。
                                            last_time = System.currentTimeMillis();
                                            handler.sendEmptyMessageDelayed(0,1000);
                                    }
                                }
                            }
                        }
                        //绘制人脸信息
   //通过自定义的 FaceOverlayView 绘制检测结果,包括:  微笑指数、眼睛睁开状态等文本信息。
                        faceOverlayView.updateFaces(faces);
                    });
                })
                .addOnFailureListener(e -> Log.e(TAG, "人脸检测失败", e))
                .addOnCompleteListener(result -> image.close());
    }

    private boolean allPermissionsGranted() {
        for (String permission : REQUIRED_PERMISSIONS) {
            if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_CODE_PERMISSIONS) {
            if (allPermissionsGranted()) {
                startCamera();
            } else {
                Toast.makeText(this, "未授予权限,无法使用相机", Toast.LENGTH_SHORT).show();
                finish();
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        cameraExecutor.shutdown();
        handler.removeMessages(0);
    }
}

4 activity_main2.xml

java 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

        <!-- CameraX预览视图 -->
        <androidx.camera.view.PreviewView
            android:id="@+id/viewFinder"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <!-- 自定义绘制人脸数据的视图 -->
        <com.sbas.mybledemohk.FaceOverlayView
            android:id="@+id/faceOverlay"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

FaceOverlayView

java 复制代码
package com.sbas.mybledemohk;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import com.google.mlkit.vision.face.Face;
import com.google.mlkit.vision.face.FaceContour;

import java.util.ArrayList;
import java.util.List;
//代码定义了一个自定义的FaceOverlayView类,它继承自View,用于在Android应用中显示基于ML Kit人脸检测的结果,包括面部轮廓、表情(如微笑、眼睛睁开等)和其他信息(如表情符号等)。下面是代码的详细解释:
public class FaceOverlayView extends View {
    private Paint facePaint;
    private Paint contourPaint;
    private Paint textPaint;
    private Paint emojiBgPaint;
    private List<Face> faces;
    private int previewWidth;
    private int previewHeight;
    private float scaleX;
    private float scaleY;
    private int imageWidth;
    private int imageHeight;
    private int rotation;
    private float emojiSize = 60f;
    private String currentMood = "😐";
    private float blinkProgress = 0f;
    private boolean isBlinking = false;

    public FaceOverlayView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        facePaint = new Paint();
        facePaint.setColor(Color.WHITE);
        facePaint.setStyle(Paint.Style.STROKE);
        facePaint.setStrokeWidth(2.0f);

        contourPaint = new Paint();
        contourPaint.setColor(Color.YELLOW);
        contourPaint.setStyle(Paint.Style.FILL);
        contourPaint.setStrokeWidth(2.0f);

        textPaint = new Paint();
        textPaint.setColor(Color.WHITE);
        textPaint.setTextSize(40f);
        textPaint.setAntiAlias(true);

        emojiBgPaint = new Paint();
        emojiBgPaint.setColor(Color.argb(100, 0, 0, 0));
        emojiBgPaint.setStyle(Paint.Style.FILL);

        faces = new ArrayList<>();
    }

    public void setImageSourceInfo(int width, int height, int rotation) {
        this.imageWidth = width;
        this.imageHeight = height;
        this.rotation = rotation;
        calculateScaleFactor();
    }

    public void setPreviewSize(int width, int height) {
        previewWidth = width;
        previewHeight = height;
        calculateScaleFactor();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        calculateScaleFactor();
    }

    private void calculateScaleFactor() {
        if (imageWidth > 0 && imageHeight > 0 && previewWidth > 0 && previewHeight > 0) {
            float targetWidth = previewWidth * 0.4f;
            scaleX = targetWidth / imageWidth;
            scaleY = scaleX;
            scaleX = (float) 3;
            scaleY = (float) 3;
            Log.i("tag",scaleX  +"   "+scaleY);
        }
    }

    public void updateFaces(List<Face> faces) {
        this.faces = faces;
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (faces == null || faces.isEmpty() || imageWidth == 0 || imageHeight == 0) {
            return;
        }



        for (Face face : faces) {
            String mood = "😐";
            if (face.getSmilingProbability() != null) {
                float smileProb = face.getSmilingProbability();
                if (smileProb > 0.8) mood = "😄";
                else if (smileProb > 0.3) mood = "🙂";
            }

            if (face.getLeftEyeOpenProbability() != null && face.getRightEyeOpenProbability() != null) {
                float leftEye = face.getLeftEyeOpenProbability();
                float rightEye = face.getRightEyeOpenProbability();
                if (leftEye < 0.3 && rightEye < 0.3) {
                    mood = "😉";
                }
            }
            currentMood = mood;

            for (FaceContour contour : face.getAllContours()) {
                switch (contour.getFaceContourType()) {
                    case FaceContour.FACE:
                        contourPaint.setColor(Color.RED);
                        break;
                    case FaceContour.LEFT_EYE:
                    case FaceContour.RIGHT_EYE:
                        contourPaint.setColor(Color.CYAN);
                        break;
                    case FaceContour.LEFT_EYEBROW_TOP:
                    case FaceContour.RIGHT_EYEBROW_TOP:
                    case FaceContour.LEFT_EYEBROW_BOTTOM:
                    case FaceContour.RIGHT_EYEBROW_BOTTOM:
                        contourPaint.setColor(Color.BLUE);
                        break;
                    case FaceContour.NOSE_BRIDGE:
                    case FaceContour.NOSE_BOTTOM:
                        contourPaint.setColor(Color.RED);
                        break;
                    case FaceContour.UPPER_LIP_TOP:
                    case FaceContour.UPPER_LIP_BOTTOM:
                    case FaceContour.LOWER_LIP_TOP:
                    case FaceContour.LOWER_LIP_BOTTOM:
                        contourPaint.setColor(Color.MAGENTA);
                        break;
                    default:
                        contourPaint.setColor(Color.YELLOW);
                }

                for (PointF point : contour.getPoints()) {
                    float x = point.x * scaleX + 0 ;
                    float y = point.y * scaleY + 0 ;
//                    Log.i("tag",x  +"   "+y);
                    canvas.drawCircle(x, y, 3f, contourPaint);

                    if (contour.getPoints().size() > 1) {
                        Paint linePaint = new Paint(contourPaint);
                        linePaint.setStyle(Paint.Style.STROKE);
                        linePaint.setStrokeWidth(2f);

                        List<PointF> points = contour.getPoints();
                        for (int i = 0; i < points.size() - 1; i++) {
                            PointF p1 = points.get(i);
                            PointF p2 = points.get(i + 1);
                            float x1 = p1.x * scaleX + 0;
                            float y1 = p1.y * scaleY + 0;
                            float x2 = p2.x * scaleX + 0;
                            float y2 = p2.y * scaleY + 0;

                            canvas.drawLine(x1, y1, x2, y2, linePaint);
                        }
                    }
                }
            }
            float offsetX = 50;
            float offsetY = 50;
            float bgLeft = offsetX;
            float bgTop = offsetY + imageHeight * scaleY + 20;
            float bgRight = bgLeft + 300;
            float bgBottom = bgTop + 150;
//            canvas.drawRoundRect(bgLeft, bgTop, bgRight, bgBottom, 20, 20, emojiBgPaint);

//            canvas.drawText(currentMood, bgLeft + 20, bgTop + 50, textPaint);
//            textPaint.setTextSize(30f);

//            if (face.getSmilingProbability() != null) {
//                String smileText = String.format("微笑指数: %.0f%%", face.getSmilingProbability() * 100);
//                canvas.drawText(smileText, bgLeft + 20, bgTop + 100, textPaint);
//            }
//
//            if (face.getLeftEyeOpenProbability() != null) {
//                String eyeText = String.format("眼睛睁开: %.0f%%",
//                        (face.getLeftEyeOpenProbability() + face.getRightEyeOpenProbability()) * 50);
//                canvas.drawText(eyeText, bgLeft + 20, bgTop + 140, textPaint);
//            }
        }

        invalidate();
    }
}

代码定义了一个自定义的FaceOverlayView类,它继承自View,用于在Android应用中显示基于ML Kit人脸检测的结果,包括面部轮廓、表情(如微笑、眼睛睁开等)和其他信息(如表情符号等)。下面是代码的详细解释:

(1) 成员变量

facePaint、contourPaint、textPaint 和 emojiBgPaint:这些是Paint对象,分别用于绘制面部边框、面部轮廓、文本(例如显示微笑指数、眼睛睁开程度等)和表情符号背景。

faces:一个List对象,用于存储当前的所有检测到的人脸。

previewWidth、previewHeight:相机预览的宽度和高度。

scaleX、scaleY:缩放因子,用于将图像的坐标映射到预览视图的坐标。

imageWidth、imageHeight:图像的宽度和高度(即传入的原始图像尺寸)。

rotation:图像的旋转角度。

emojiSize:表情符号的大小。

blinkProgress 和 isBlinking:用于处理眼睛睁开状态和眨眼的进度。

相关推荐
liang_jy39 分钟前
Android SparseArray
android·源码
victory04311 小时前
论文设计和撰写1
人工智能·深度学习·机器学习
liang_jy1 小时前
Activity 启动流程扩展篇(一)—— startActivityInner 任务决策全解析
android·源码
NPE~2 小时前
[App逆向]脱壳实战
android·教程·逆向·android逆向·逆向分析
木易 士心3 小时前
别再只会用 drawCircle 了!一文搞懂 Android Canvas 底层机制
android
AtOR CUES4 小时前
MySQL——表操作及查询
android·mysql·adb
怣疯knight5 小时前
安卓App无法增加自定义图片作为图标功能
android
有为少年6 小时前
从概率估计到“LLM 训练是有损压缩”
人工智能·线性代数·机器学习·计算机视觉·矩阵
jinanwuhuaguo7 小时前
OpenClaw联邦之心——从孤岛记忆到硅基集体潜意识的拓扑学革命(第二十三篇)
android·人工智能·kotlin·拓扑学·openclaw
春风有信7 小时前
【DM】DDPM与DDIM的数学原理
人工智能·深度学习·机器学习