From 6eef3a1fb4d14d5f7f10e66e146ebc2bf5adcc0b Mon Sep 17 00:00:00 2001 From: jjLv <1981429112@qq.com> Date: Thu, 4 Dec 2025 18:03:25 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E5=85=A5=E6=89=8B=E5=BF=83=E6=89=8B?= =?UTF-8?q?=E8=83=8C=E8=AF=86=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/proguard-rules.pro | 9 +++- .../canteen/fragment/BackHandFragment.java | 31 +++++++++-- .../bonus/canteen/fragment/FaceFragment.java | 2 +- .../bonus/canteen/fragment/PalmFragment.java | 32 +++++++++-- .../canteen/utils/onnx/HandClassifier.java | 54 ++++++++++++++----- app/src/main/res/layout/activity_plam.xml | 4 +- 6 files changed, 106 insertions(+), 26 deletions(-) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 4b64f96..7952051 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -311,4 +311,11 @@ # 保留无参构造方法 -keepclassmembers class * { public (); -} \ No newline at end of file +} + + +-keep class ai.onnxruntime.** { *; } +-keep interface ai.onnxruntime.** { *; } + +-keep class com.google.gson.** { *; } +-keep class com.alibaba.fastjson2.** { *; } \ No newline at end of file diff --git a/app/src/main/java/com/bonus/canteen/fragment/BackHandFragment.java b/app/src/main/java/com/bonus/canteen/fragment/BackHandFragment.java index f0e6a39..c60e00c 100644 --- a/app/src/main/java/com/bonus/canteen/fragment/BackHandFragment.java +++ b/app/src/main/java/com/bonus/canteen/fragment/BackHandFragment.java @@ -21,6 +21,8 @@ import android.Manifest; import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.ImageFormat; import android.graphics.Matrix; import android.graphics.RectF; @@ -63,6 +65,7 @@ import com.bonus.canteen.entity.PhotoResponse; import com.bonus.canteen.utils.OkHttpService; import com.bonus.canteen.utils.ThreadPoolManager; import com.bonus.canteen.utils.WorkConfig; +import com.bonus.canteen.utils.onnx.HandClassifier; import com.bonus.canteen.utils.sound.Sound; import com.bonus.canteen.utils.sound.SoundManager; import com.google.gson.Gson; @@ -89,7 +92,7 @@ import cn.hutool.core.util.ObjectUtil; public class BackHandFragment extends BaseFragment { private final Handler mHandler = new Handler(Looper.getMainLooper()); private OkHttpService service = new OkHttpService(); - private int countdownSeconds = 4; + private int countdownSeconds = 3; private Runnable countdownRunnable; private SurfaceView mSurfaceView; @@ -107,7 +110,8 @@ public class BackHandFragment extends BaseFragment { private Size previewSize; private String cameraId; private static final String TAG = "PalmFragment"; - + // 1. 定义变量 + private HandClassifier classifier; @NonNull @Override protected ActivityBackHandBinding viewBindingInflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, boolean attachToRoot) { @@ -116,6 +120,8 @@ public class BackHandFragment extends BaseFragment { @Override protected void initViews() { + // 2. 初始化 (在 onCreate 中执行一次) + classifier = new HandClassifier(requireContext()); SoundManager.getInstance().play(Sound.createSimpleSound(R.raw.start_back_end)); Log.e("PalmFragment", "initViews: 手背界面初始化"); // 初始化摄像头预览 @@ -126,7 +132,7 @@ public class BackHandFragment extends BaseFragment { } private void startUpdateTime() { - countdownSeconds = 4; + countdownSeconds = 3; countdownRunnable = new Runnable() { @SuppressLint("SetTextI18n") @Override @@ -373,8 +379,23 @@ public class BackHandFragment extends BaseFragment { ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); - // 保存图片 - saveImage(bytes); + try { + ThreadPoolManager.getExecutor().execute(()->{ + // 保存图片 + Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); + String result = classifier.predict(bitmap); + if (result != null && result.equals("back_of_hand")) { + Log.e(TAG, "识别通过: " + result); + getActivity().runOnUiThread(() -> saveImage(bytes)); + } else { + Log.e(TAG, "识别失败: " + result); + // 原拍照方法(包含CameraCaptureSession.capture调用) + getActivity().runOnUiThread(this::takePicture); + } + }); + }catch (Exception e) { + Log.e(TAG, "处理图像线程错误: " + e.getMessage()); + } } } catch (Exception e) { Log.e(TAG, "处理图像错误: " + e.getMessage()); diff --git a/app/src/main/java/com/bonus/canteen/fragment/FaceFragment.java b/app/src/main/java/com/bonus/canteen/fragment/FaceFragment.java index 1d7a19a..5ce2ddf 100644 --- a/app/src/main/java/com/bonus/canteen/fragment/FaceFragment.java +++ b/app/src/main/java/com/bonus/canteen/fragment/FaceFragment.java @@ -121,7 +121,7 @@ public class FaceFragment extends BaseFragment { /** * 活体检测的开关 */ - private boolean livenessDetect = true; + private boolean livenessDetect = false; /** * 用于记录人脸识别相关状态 */ diff --git a/app/src/main/java/com/bonus/canteen/fragment/PalmFragment.java b/app/src/main/java/com/bonus/canteen/fragment/PalmFragment.java index 6a6c3d3..0cb5257 100644 --- a/app/src/main/java/com/bonus/canteen/fragment/PalmFragment.java +++ b/app/src/main/java/com/bonus/canteen/fragment/PalmFragment.java @@ -21,6 +21,8 @@ import android.Manifest; import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.ImageFormat; import android.graphics.Matrix; import android.graphics.RectF; @@ -63,6 +65,7 @@ import com.bonus.canteen.entity.PhotoResponse; import com.bonus.canteen.utils.OkHttpService; import com.bonus.canteen.utils.ThreadPoolManager; import com.bonus.canteen.utils.WorkConfig; +import com.bonus.canteen.utils.onnx.HandClassifier; import com.bonus.canteen.utils.sound.Sound; import com.bonus.canteen.utils.sound.SoundManager; import com.google.gson.Gson; @@ -89,7 +92,7 @@ import cn.hutool.core.util.ObjectUtil; public class PalmFragment extends BaseFragment { private final Handler mHandler = new Handler(Looper.getMainLooper()); private OkHttpService service = new OkHttpService(); - private int countdownSeconds = 4; + private int countdownSeconds = 3; private Runnable countdownRunnable; private SurfaceView mSurfaceView; @@ -107,7 +110,8 @@ public class PalmFragment extends BaseFragment { private Size previewSize; private String cameraId; private static final String TAG = "PalmFragment"; - + // 1. 定义变量 + private HandClassifier classifier; @NonNull @Override protected ActivityPlamBinding viewBindingInflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, boolean attachToRoot) { @@ -117,6 +121,8 @@ public class PalmFragment extends BaseFragment { @Override protected void initViews() { Log.e("PalmFragment", "initViews: 手心界面初始化"); + // 2. 初始化 (在 onCreate 中执行一次) + classifier = new HandClassifier(requireContext()); SoundManager.getInstance().play(Sound.createSimpleSound(R.raw.start_plam)); // 初始化摄像头预览 mSurfaceView = findViewById(R.id.take_photo_surface_view); @@ -126,7 +132,7 @@ public class PalmFragment extends BaseFragment { } private void startUpdateTime() { - countdownSeconds = 4; + countdownSeconds = 3; countdownRunnable = new Runnable() { @SuppressLint("SetTextI18n") @Override @@ -373,8 +379,24 @@ public class PalmFragment extends BaseFragment { ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); - // 保存图片 - saveImage(bytes); + try { + ThreadPoolManager.getExecutor().execute(()->{ + // 保存图片 + Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); + String result = classifier.predict(bitmap); + if (result != null && result.equals("plam")) { + Log.e(TAG, "识别通过: " + result); + getActivity().runOnUiThread(() -> saveImage(bytes)); + } else { + Log.e(TAG, "识别失败: " + result); + // 原拍照方法(包含CameraCaptureSession.capture调用) + getActivity().runOnUiThread(this::takePicture); + } + }); + }catch (Exception e) { + Log.e(TAG, "处理图像错误: " + e.getMessage()); + } + } } catch (Exception e) { XToastUtils.error("处理图像时出错 "); diff --git a/app/src/main/java/com/bonus/canteen/utils/onnx/HandClassifier.java b/app/src/main/java/com/bonus/canteen/utils/onnx/HandClassifier.java index a564c56..12d824a 100644 --- a/app/src/main/java/com/bonus/canteen/utils/onnx/HandClassifier.java +++ b/app/src/main/java/com/bonus/canteen/utils/onnx/HandClassifier.java @@ -62,30 +62,38 @@ public class HandClassifier { public String predict(Bitmap image) { if (session == null) return "Error"; try { - // 1. 预处理:缩放图片到 160x160 (必须与训练一致) + // 1. 预处理 (保持不变) Bitmap resized = Bitmap.createScaledBitmap(image, 160, 160, true); - - // 2. 转换数据:Bitmap -> FloatBuffer (HWC 转 CHW,归一化) FloatBuffer inputBuffer = preprocess(resized); - // 3. 创建 Tensor [1, 3, 160, 160] + // 2. 推理 (保持不变) long[] shape = new long[]{1, 3, 160, 160}; OnnxTensor tensor = OnnxTensor.createTensor(env, inputBuffer, shape); - - // 4. 运行推理 String inputName = session.getInputNames().iterator().next(); OrtSession.Result result = session.run(Collections.singletonMap(inputName, tensor)); - // 5. 解析结果 + // 3. 解析结果 float[][] output = (float[][]) result.get(0).getValue(); - float[] scores = output[0]; // [score_back, score_plam] + float[] rawScores = output[0]; // 原始分数 (Logits) - // 6. 简单的比较大小 (Argmax) - // 如果 scores[1] > scores[0],说明是手心 - if (scores[1] > scores[0]) { - return "plam"; // 手心 + // ⚠ 新增步骤:计算概率 (Softmax) + float[] probs = softmax(rawScores); + + float probBack = probs[0]; // 手背概率 + float probPalm = probs[1]; // 手心概率 + + // 4. 阈值判断 (关键步骤) + // 设定一个严格的阈值,比如 0.9 (90%) + // 只有当某一个类别的概率非常高时,才认为是有效识别 + float THRESHOLD = 0.65f; + + if (probPalm > THRESHOLD) { + return "plam"; // 确信是手心 + } else if (probBack > THRESHOLD) { + return "back_of_hand"; // 确信是手背 } else { - return "back_of_hand"; // 手背 + // 两个概率都不高,或者都很接近 (比如 0.6 vs 0.4),说明画面不清晰或没有手 + return "none"; } } catch (Exception e) { @@ -94,6 +102,26 @@ public class HandClassifier { } } + // ⚠ 新增辅助方法:Softmax 函数 + // 将模型输出的原始数值转换为 0.0 ~ 1.0 的概率值 + private float[] softmax(float[] input) { + float[] output = new float[input.length]; + float sum = 0.0f; + + // 为了数值稳定性,先减去最大值 + float max = Float.NEGATIVE_INFINITY; + for (float v : input) max = Math.max(max, v); + + for (int i = 0; i < input.length; i++) { + output[i] = (float) Math.exp(input[i] - max); + sum += output[i]; + } + + for (int i = 0; i < input.length; i++) { + output[i] /= sum; + } + return output; + } // 图像数据预处理:将像素点转为 YOLO 需要的格式 private FloatBuffer preprocess(Bitmap bitmap) { int width = 160; diff --git a/app/src/main/res/layout/activity_plam.xml b/app/src/main/res/layout/activity_plam.xml index 2ebc85f..87b2954 100644 --- a/app/src/main/res/layout/activity_plam.xml +++ b/app/src/main/res/layout/activity_plam.xml @@ -36,11 +36,13 @@ + + android:layout_gravity="center" /> +