加入手心手背识别
This commit is contained in:
parent
4d2f0aa40b
commit
6eef3a1fb4
|
|
@ -311,4 +311,11 @@
|
|||
# 保留无参构造方法
|
||||
-keepclassmembers class * {
|
||||
public <init>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-keep class ai.onnxruntime.** { *; }
|
||||
-keep interface ai.onnxruntime.** { *; }
|
||||
|
||||
-keep class com.google.gson.** { *; }
|
||||
-keep class com.alibaba.fastjson2.** { *; }
|
||||
|
|
@ -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<ActivityBackHandBinding> {
|
||||
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<ActivityBackHandBinding> {
|
|||
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<ActivityBackHandBinding> {
|
|||
|
||||
@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<ActivityBackHandBinding> {
|
|||
|
||||
}
|
||||
private void startUpdateTime() {
|
||||
countdownSeconds = 4;
|
||||
countdownSeconds = 3;
|
||||
countdownRunnable = new Runnable() {
|
||||
@SuppressLint("SetTextI18n")
|
||||
@Override
|
||||
|
|
@ -373,8 +379,23 @@ public class BackHandFragment extends BaseFragment<ActivityBackHandBinding> {
|
|||
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());
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ public class FaceFragment extends BaseFragment<ActivityFaceBinding> {
|
|||
/**
|
||||
* 活体检测的开关
|
||||
*/
|
||||
private boolean livenessDetect = true;
|
||||
private boolean livenessDetect = false;
|
||||
/**
|
||||
* 用于记录人脸识别相关状态
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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<ActivityPlamBinding> {
|
||||
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<ActivityPlamBinding> {
|
|||
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<ActivityPlamBinding> {
|
|||
@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<ActivityPlamBinding> {
|
|||
|
||||
}
|
||||
private void startUpdateTime() {
|
||||
countdownSeconds = 4;
|
||||
countdownSeconds = 3;
|
||||
countdownRunnable = new Runnable() {
|
||||
@SuppressLint("SetTextI18n")
|
||||
@Override
|
||||
|
|
@ -373,8 +379,24 @@ public class PalmFragment extends BaseFragment<ActivityPlamBinding> {
|
|||
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("处理图像时出错 ");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -36,11 +36,13 @@
|
|||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<SurfaceView
|
||||
android:id="@+id/take_photo_surface_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"/>
|
||||
android:layout_gravity="center" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/left"
|
||||
android:layout_width="465dp"
|
||||
|
|
|
|||
Loading…
Reference in New Issue