diff --git a/app/build.gradle b/app/build.gradle index a0b1ee1..c112720 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -170,6 +170,10 @@ dependencies { implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.3' implementation 'org.bouncycastle:bcprov-jdk15to18:1.77' + + // 微软官方 ONNX Runtime 库 + implementation 'com.microsoft.onnxruntime:onnxruntime-android:1.16.0' + } //自动添加X-Library依赖 apply from: 'x-library.gradle' diff --git a/app/src/main/assets/best.onnx b/app/src/main/assets/best.onnx new file mode 100644 index 0000000..90c59d7 Binary files /dev/null and b/app/src/main/assets/best.onnx differ diff --git a/app/src/main/java/com/bonus/canteen/activity/MainActivity.java b/app/src/main/java/com/bonus/canteen/activity/MainActivity.java index 4161454..668d79d 100644 --- a/app/src/main/java/com/bonus/canteen/activity/MainActivity.java +++ b/app/src/main/java/com/bonus/canteen/activity/MainActivity.java @@ -344,14 +344,15 @@ public class MainActivity extends BaseActivity implements C pixelTempList.add(numList.get(i) / 100.0); } Double maxValue = pixelTempList.isEmpty() ? null : Collections.max(pixelTempList); + maxValue = maxValue == null ? 0.0 : maxValue + 3.0; try { pixelTempList.remove(0); pixelTempList.remove(0); if (maxValue < 36.5) { - maxValue = 36.5 + Math.random(); + maxValue = 36.5 + Math.random() * 0.5; } if (maxValue > 39.5) { - maxValue = 38.5 + Math.random(); + maxValue = 38.5 + Math.random() * 0.5; } DecimalFormat df = new DecimalFormat("#.0"); maxValue = Double.parseDouble(df.format(maxValue)); 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 6ab6eb8..f0e6a39 100644 --- a/app/src/main/java/com/bonus/canteen/fragment/BackHandFragment.java +++ b/app/src/main/java/com/bonus/canteen/fragment/BackHandFragment.java @@ -89,7 +89,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 = 3; + private int countdownSeconds = 4; private Runnable countdownRunnable; private SurfaceView mSurfaceView; @@ -126,7 +126,7 @@ public class BackHandFragment extends BaseFragment { } private void startUpdateTime() { - countdownSeconds = 3; + countdownSeconds = 4; countdownRunnable = new Runnable() { @SuppressLint("SetTextI18n") @Override @@ -476,7 +476,7 @@ public class BackHandFragment extends BaseFragment { Log.w(TAG, "相机启动超时"); recoverCamera(); } - }, 3000); // 3秒超时 + }, 4000); // 3秒超时 } // 停止后台线程 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 68b5dac..1d7a19a 100644 --- a/app/src/main/java/com/bonus/canteen/fragment/FaceFragment.java +++ b/app/src/main/java/com/bonus/canteen/fragment/FaceFragment.java @@ -396,7 +396,7 @@ public class FaceFragment extends BaseFragment { Camera.Size lastPreviewSize = previewSize; previewSize = camera.getParameters().getPreviewSize(); drawHelper = new DrawHelper(previewSize.width, previewSize.height, previewView.getWidth(), previewView.getHeight(), displayOrientation - , cameraId, false, false, false); + , cameraId, true, false, false); // 切换相机的时候可能会导致预览尺寸发生变化 if (faceHelper == null || @@ -475,7 +475,7 @@ public class FaceFragment extends BaseFragment { cameraHelper = new CameraHelper.Builder() .previewViewSize(new Point(previewView.getMeasuredWidth(), previewView.getMeasuredHeight())) .rotation(0) - .isMirror(false) + .isMirror(true) .previewOn(previewView) .cameraListener(cameraListener) .specificCameraId(0) 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 8211063..6a6c3d3 100644 --- a/app/src/main/java/com/bonus/canteen/fragment/PalmFragment.java +++ b/app/src/main/java/com/bonus/canteen/fragment/PalmFragment.java @@ -89,7 +89,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 = 3; + private int countdownSeconds = 4; private Runnable countdownRunnable; private SurfaceView mSurfaceView; @@ -126,7 +126,7 @@ public class PalmFragment extends BaseFragment { } private void startUpdateTime() { - countdownSeconds = 3; + countdownSeconds = 4; countdownRunnable = new Runnable() { @SuppressLint("SetTextI18n") @Override diff --git a/app/src/main/java/com/bonus/canteen/service/data/UpdateBasicData.java b/app/src/main/java/com/bonus/canteen/service/data/UpdateBasicData.java index dfe6b4e..cc784d5 100644 --- a/app/src/main/java/com/bonus/canteen/service/data/UpdateBasicData.java +++ b/app/src/main/java/com/bonus/canteen/service/data/UpdateBasicData.java @@ -194,24 +194,30 @@ public class UpdateBasicData { } private void uploadToServer(List uploadList) { JSONObject json = new JSONObject(); - json.put("userFaceList", uploadList); - String jsonString = json.toString(); - Log.i("getPersonMessage jsonString", jsonString); - // 定义 JSON 的 MediaType - MediaType mediaType = MediaType.parse(MEDIA_TYPE); - // 创建 RequestBody - RequestBody body = RequestBody.create(mediaType, jsonString); - String result = service.httpPost(WorkConfig.getBaseUrl() + UrlConfig.SAVE_APP_FACE_EIGENVALUE, body, context); - if (!ObjectUtil.isEmpty(result)) { - JSONObject jsonObject = JSONObject.parseObject(result); - if (jsonObject.getString("msg").equals("操作成功") || jsonObject.getInteger("code") == 200) { - Log.d(TAG, "人脸特征值上传成功"); + uploadList.forEach(item -> { + List list = new ArrayList<>(); + list.add(item); + json.put("userFaceList", list); + String jsonString = json.toString(); + Log.i("getPersonMessage jsonString", jsonString); + // 定义 JSON 的 MediaType + MediaType mediaType = MediaType.parse(MEDIA_TYPE); + // 创建 RequestBody + RequestBody body = RequestBody.create(mediaType, jsonString); + String result = service.httpPost("http://192.168.20.234:48388" + UrlConfig.SAVE_APP_FACE_EIGENVALUE, body, context); + if (!ObjectUtil.isEmpty(result)) { + JSONObject jsonObject = JSONObject.parseObject(result); + if (jsonObject.getString("msg").equals("操作成功") || jsonObject.getInteger("code") == 200) { + Log.d(TAG, "人脸特征值上传成功"); + } else { + Log.d(TAG, "人脸特征值上传失败"); + } } else { Log.d(TAG, "人脸特征值上传失败"); } - } else { - Log.d(TAG, "人脸特征值上传失败"); - } + }); + + } public void faceRecognition(CustPhotoFulInfo custPhotoFulInfo,List uploadList) { 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 new file mode 100644 index 0000000..a564c56 --- /dev/null +++ b/app/src/main/java/com/bonus/canteen/utils/onnx/HandClassifier.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2025 xuexiangjys(xuexiangjys@163.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.bonus.canteen.utils.onnx; + + +import android.content.Context; +import android.graphics.Bitmap; +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.nio.FloatBuffer; +import java.util.Collections; + +import ai.onnxruntime.OnnxTensor; +import ai.onnxruntime.OrtEnvironment; +import ai.onnxruntime.OrtSession; + +public class HandClassifier { + private OrtEnvironment env; + private OrtSession session; + + // ⚠️ 注意:这里的顺序必须和训练时的文件夹顺序一致! + // 0: back_of_hand (手背), 1: plam (手心 - 你的拼写) + private final String[] LABELS = {"back_of_hand", "plam"}; + + // 构造函数:加载模型 + public HandClassifier(Context context) { + try { + env = OrtEnvironment.getEnvironment(); + // 从 assets 文件夹读取 best.onnx + byte[] modelData = readAssetFile(context, "best.onnx"); + + OrtSession.SessionOptions opts = new OrtSession.SessionOptions(); + // RK3288 是 4 核 CPU,我们设置 4 线程来榨干性能 + opts.setIntraOpNumThreads(4); + + session = env.createSession(modelData, opts); + Log.d("HandClassifier", "模型加载成功!"); + } catch (Exception e) { + e.printStackTrace(); + Log.e("HandClassifier", "模型加载失败: " + e.getMessage()); + } + } + + // --- 核心预测方法 --- + public String predict(Bitmap image) { + if (session == null) return "Error"; + try { + // 1. 预处理:缩放图片到 160x160 (必须与训练一致) + Bitmap resized = Bitmap.createScaledBitmap(image, 160, 160, true); + + // 2. 转换数据:Bitmap -> FloatBuffer (HWC 转 CHW,归一化) + FloatBuffer inputBuffer = preprocess(resized); + + // 3. 创建 Tensor [1, 3, 160, 160] + 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. 解析结果 + float[][] output = (float[][]) result.get(0).getValue(); + float[] scores = output[0]; // [score_back, score_plam] + + // 6. 简单的比较大小 (Argmax) + // 如果 scores[1] > scores[0],说明是手心 + if (scores[1] > scores[0]) { + return "plam"; // 手心 + } else { + return "back_of_hand"; // 手背 + } + + } catch (Exception e) { + e.printStackTrace(); + return "Error"; + } + } + + // 图像数据预处理:将像素点转为 YOLO 需要的格式 + private FloatBuffer preprocess(Bitmap bitmap) { + int width = 160; + int height = 160; + int[] pixels = new int[width * height]; + // 获取所有像素点 + bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + + FloatBuffer buffer = FloatBuffer.allocate(3 * width * height); + + for (int i = 0; i < pixels.length; ++i) { + int val = pixels[i]; + // 提取 RGB 分量并归一化 (0-255 -> 0.0-1.0) + float r = ((val >> 16) & 0xFF) / 255.0f; + float g = ((val >> 8) & 0xFF) / 255.0f; + float b = (val & 0xFF) / 255.0f; + + // 填充 Buffer,顺序必须是 R... G... B... (CHW格式) + buffer.put(i, r); + buffer.put(width * height + i, g); + buffer.put(2 * width * height + i, b); + } + buffer.rewind(); // 重置指针 + return buffer; + } + + // 辅助工具:读取 assets 文件 + private byte[] readAssetFile(Context context, String fileName) throws Exception { + InputStream is = context.getAssets().open(fileName); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int nRead; + byte[] data = new byte[16384]; + while ((nRead = is.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + buffer.flush(); + return buffer.toByteArray(); + } +}