这章增加几个简单的手势,输出结果
1: 代码如下
在上章修改
MainActivity.java
package com.example.testhand;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.graphics.SurfaceTexture;
import android.os.Bundle;
/
import android.provider.MediaStore;
import android.util.Log;
import android.util.Size;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
//
import com.google.mediapipe.components.CameraHelper;
import com.google.mediapipe.components.CameraXPreviewHelper;
import com.google.mediapipe.components.ExternalTextureConverter;
import com.google.mediapipe.components.FrameProcessor;
import com.google.mediapipe.components.PermissionHelper;
import com.google.mediapipe.formats.proto.LandmarkProto.NormalizedLandmarkList;
import com.google.mediapipe.formats.proto.LandmarkProto.NormalizedLandmark;
import com.google.mediapipe.framework.AndroidAssetUtil;
import com.google.mediapipe.framework.AndroidPacketGetter;
import com.google.mediapipe.framework.Packet;
import com.google.mediapipe.framework.PacketGetter;
import com.google.mediapipe.glutil.EglManager;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.mediapipe.framework.AndroidPacketCreator;
///
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
// 资源文件和流输出名
private static final String BINARY_GRAPH_NAME = "hand_tracking_mobile_gpu.binarypb";
private static final String INPUT_VIDEO_STREAM_NAME = "input_video";
private static final String OUTPUT_VIDEO_STREAM_NAME = "output_video";
private static final String OUTPUT_HAND_PRESENCE_STREAM_NAME = "hand_presence";
private static final String OUTPUT_LANDMARKS_STREAM_NAME = "hand_landmarks";
private static final String INPUT_NUM_HANDS_SIDE_PACKET_NAME = "num_hands";
// Max number of hands to detect/process.
private static final int NUM_HANDS = 2;
private static final String INPUT_MODEL_COMPLEXITY = "model_complexity";
private SurfaceTexture previewFrameTexture;
private SurfaceView previewDisplayView;
private EglManager eglManager;
private FrameProcessor processor;
private ExternalTextureConverter converter;
private CameraXPreviewHelper cameraHelper;
private boolean handPresence;
// 所使用的摄像头
private static boolean USE_FRONT_CAMERA = true;
Button button_change_camera,button_change_picture_show ;
// 因为OpenGL表示图像时假设图像原点在左下角,而MediaPipe通常假设图像原点在左上角,所以要翻转
private static final boolean FLIP_FRAMES_VERTICALLY = true;
private TextView showtext;
// 加载动态库
static {
System.loadLibrary("mediapipe_jni");
System.loadLibrary("opencv_java3");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
showtext = findViewById(R.id.showtext);
button_change_camera= findViewById(R.id.button_change_camera);
button_change_picture_show = findViewById(R.id.button_change_picture_show);
previewDisplayView = new SurfaceView(this);
setupPreviewDisplayView();
// 获取权限
PermissionHelper.checkAndRequestCameraPermissions(this);
// 初始化assets管理器,以便MediaPipe应用资源
AndroidAssetUtil.initializeNativeAssetManager(this);
eglManager = new EglManager(null);
// 通过加载获取一个帧处理器
processor = new FrameProcessor(this,
eglManager.getNativeContext(),
BINARY_GRAPH_NAME,
INPUT_VIDEO_STREAM_NAME,
OUTPUT_VIDEO_STREAM_NAME);
processor.getVideoSurfaceOutput().setFlipY(FLIP_FRAMES_VERTICALLY);
AndroidPacketCreator packetCreator = processor.getPacketCreator();
Map<String, Packet> inputSidePackets = new HashMap<>();
inputSidePackets.put(INPUT_NUM_HANDS_SIDE_PACKET_NAME, packetCreator.createInt32(NUM_HANDS));
// inputSidePackets.put(
// INPUT_MODEL_COMPLEXITY, packetCreator.createInt32(0)); //"modelComplexity": "0" # 0=lite, 1=heavy, not specified=heavy
processor.setInputSidePackets(inputSidePackets);
// 获取手的关键点模型输出
processor.addPacketCallback(
OUTPUT_LANDMARKS_STREAM_NAME,
(packet) -> {
Log.v(TAG, "Received multi-hand landmarks packet.");
List<NormalizedLandmarkList> multiHandLandmarks =
PacketGetter.getProtoVector(packet, NormalizedLandmarkList.parser());
Log.v(
TAG,
"[TS:"
+ packet.getTimestamp()
+ "] "
+ getMultiHandLandmarksDebugString(multiHandLandmarks));
//
int numHands = multiHandLandmarks.size();
ArrayList<String> sites = new ArrayList<>(); //eh add
for (int i = 0; i < numHands; ++i) {
String str = GetGestureResult(GestureRecognition(multiHandLandmarks.get(i).getLandmarkList()));
sites.add("[" + i + "] " + str);
// Log.d(TAG, "[" + numHands + "] " + str);
}
showtext.setText(sites.toString());
/
});
//切换摄像头
button_change_camera.setOnClickListener(
v -> {
USE_FRONT_CAMERA = !USE_FRONT_CAMERA ;
previewDisplayView.setVisibility(View.GONE);
converter.close();
cameraHelper = null ;
converter = new ExternalTextureConverter(eglManager.getContext(),2);
converter.setFlipY(FLIP_FRAMES_VERTICALLY);
converter.setConsumer(processor);
if (PermissionHelper.cameraPermissionsGranted(this)) {
startCamera();
}
});
// button_change_picture_show.setOnClickListener(
// v->{
// int show = previewDisplayView.getVisibility() ;
// if(View.GONE == show ){
// previewDisplayView.setVisibility(View.VISIBLE);
// }else if (View.VISIBLE == show ){
// previewDisplayView.setVisibility(View.GONE);
// }
// });
}
private String getMultiHandLandmarksDebugString(List<NormalizedLandmarkList> multiHandLandmarks) {
if (multiHandLandmarks.isEmpty()) {
return "No hand landmarks";
}
String multiHandLandmarksStr = "Number of hands detected: " + multiHandLandmarks.size() + "\n";
int handIndex = 0;
for (NormalizedLandmarkList landmarks : multiHandLandmarks) {
multiHandLandmarksStr +=
"\t#Hand landmarks for hand[" + handIndex + "]: " + landmarks.getLandmarkCount() + "\n";
int landmarkIndex = 0;
for (NormalizedLandmark landmark : landmarks.getLandmarkList()) {
multiHandLandmarksStr +=
"\t\tLandmark ["
+ landmarkIndex
+ "]: ("
+ landmark.getX()
+ ", "
+ landmark.getY()
+ ", "
+ landmark.getZ()
+ ")\n";
++landmarkIndex;
}
++handIndex;
}
return multiHandLandmarksStr;
}
@Override
protected void onResume() {
super.onResume();
converter = new ExternalTextureConverter(eglManager.getContext(),2);
converter.setFlipY(FLIP_FRAMES_VERTICALLY);
converter.setConsumer(processor);
if (PermissionHelper.cameraPermissionsGranted(this)) {
startCamera();
}
}
@Override
protected void onPause() {
super.onPause();
converter.close();
previewDisplayView.setVisibility(View.GONE);
cameraHelper = null ;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
PermissionHelper.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
// 计算最佳的预览大小
protected Size computeViewSize(int width, int height) {
return new Size(width, height);
}
protected void onPreviewDisplaySurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// 设置预览大小
Size viewSize = computeViewSize(width, height);
Size displaySize = cameraHelper.computeDisplaySizeFromViewSize(viewSize);
// 根据是否旋转调整预览图像大小
boolean isCameraRotated = cameraHelper.isCameraRotated();
converter.setSurfaceTextureAndAttachToGLContext(
previewFrameTexture,
isCameraRotated ? displaySize.getHeight() : displaySize.getWidth(),
isCameraRotated ? displaySize.getWidth() : displaySize.getHeight());
}
private void setupPreviewDisplayView() {
previewDisplayView.setVisibility(View.GONE);
ViewGroup viewGroup = findViewById(R.id.preview_display_layout);
viewGroup.addView(previewDisplayView);
previewDisplayView
.getHolder()
.addCallback(
new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
processor.getVideoSurfaceOutput().setSurface(holder.getSurface());
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
onPreviewDisplaySurfaceChanged(holder, format, width, height);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
processor.getVideoSurfaceOutput().setSurface(null);
}
});
}
// 相机启动后事件
protected void onCameraStarted(SurfaceTexture surfaceTexture) {
// 显示预览
previewFrameTexture = surfaceTexture;
previewDisplayView.setVisibility(View.VISIBLE);
}
// 设置相机大小
protected Size cameraTargetResolution() {
return null;
}
// 启动相机
public void startCamera() {
cameraHelper = new CameraXPreviewHelper();
cameraHelper.setOnCameraStartedListener(this::onCameraStarted);
CameraHelper.CameraFacing cameraFacing =
USE_FRONT_CAMERA ? CameraHelper.CameraFacing.FRONT : CameraHelper.CameraFacing.BACK;
cameraHelper.startCamera(this, cameraFacing, null, cameraTargetResolution());
}
// 解析关键点
private static String getLandmarksDebugString(NormalizedLandmarkList landmarks) {
int landmarkIndex = 0;
StringBuilder landmarksString = new StringBuilder();
for (NormalizedLandmark landmark : landmarks.getLandmarkList()) {
landmarksString.append("\t\tLandmark[").append(landmarkIndex).append("]: (").append(landmark.getX()).append(", ").append(landmark.getY()).append(", ").append(landmark.getZ()).append(")\n");
++landmarkIndex;
}
return landmarksString.toString();
}
//eh add
//手势识别
private int GestureRecognition( List<NormalizedLandmark> handLandmarkList){
if (handLandmarkList.size() != 21)
return -1;
// 大拇指角度
// NormalizedLandmark thumb_vec1;
float thumb_vec1_x = handLandmarkList.get(0).getX() - handLandmarkList.get(2).getX() ;
float thumb_vec1_y = handLandmarkList.get(0).getY() - handLandmarkList.get(2).getY() ;
float thumb_vec2_x = handLandmarkList.get(3).getX() - handLandmarkList.get(4).getX() ;
float thumb_vec2_y = handLandmarkList.get(3).getY() - handLandmarkList.get(4).getY() ;
float thumb_angle = Vector2DAngle(thumb_vec1_x, thumb_vec1_y,thumb_vec2_x, thumb_vec2_y);
// 食指角度
float index_vec1_x = handLandmarkList.get(0).getX() - handLandmarkList.get(6).getX() ;
float index_vec1_y = handLandmarkList.get(0).getY() - handLandmarkList.get(6).getY() ;
float index_vec2_x = handLandmarkList.get(7).getX() - handLandmarkList.get(8).getX() ;
float index_vec2_y = handLandmarkList.get(7).getY() - handLandmarkList.get(8).getY() ;
float index_angle = Vector2DAngle(index_vec1_x, index_vec1_y,index_vec2_x,index_vec2_y);
// 中指角度
float middle_vec1_x = handLandmarkList.get(0).getX() - handLandmarkList.get(10).getX() ;
float middle_vec1_y = handLandmarkList.get(0).getY() - handLandmarkList.get(10).getY() ;
float middle_vec2_x = handLandmarkList.get(11).getX() - handLandmarkList.get(12).getX() ;
float middle_vec2_y = handLandmarkList.get(11).getY() - handLandmarkList.get(12).getY() ;
float middle_angle = Vector2DAngle(middle_vec1_x, middle_vec1_y,middle_vec2_x,middle_vec2_y);
// 无名指角度
float ring_vec1_x = handLandmarkList.get(0).getX() - handLandmarkList.get(14).getX() ;
float ring_vec1_y = handLandmarkList.get(0).getY() - handLandmarkList.get(14).getY() ;
float ring_vec2_x = handLandmarkList.get(15).getX() - handLandmarkList.get(16).getX() ;
float ring_vec2_y = handLandmarkList.get(15).getY() - handLandmarkList.get(16).getY() ;
float ring_angle = Vector2DAngle(ring_vec1_x, ring_vec1_y,ring_vec2_x,ring_vec2_y);
// 小拇指角度
float pink_vec1_x = handLandmarkList.get(0).getX() - handLandmarkList.get(18).getX() ;
float pink_vec1_y = handLandmarkList.get(0).getY() - handLandmarkList.get(18).getY() ;
float pink_vec2_x = handLandmarkList.get(19).getX() - handLandmarkList.get(20).getX() ;
float pink_vec2_y = handLandmarkList.get(19).getY() - handLandmarkList.get(20).getY() ;
float pink_angle = Vector2DAngle(pink_vec1_x, pink_vec1_y,pink_vec2_x,pink_vec2_y);
// 根据角度判断手势
float angle_threshold = 65;
float thumb_angle_threshold = 40;
int result = -1;
if ((thumb_angle > thumb_angle_threshold) && (index_angle > angle_threshold) && (middle_angle > angle_threshold) && (ring_angle > angle_threshold) && (pink_angle > angle_threshold))
result = 9;
else if ((thumb_angle > 5) && (index_angle < angle_threshold) && (middle_angle > angle_threshold) && (ring_angle > angle_threshold) && (pink_angle > angle_threshold))
result = 1;
else if ((thumb_angle > thumb_angle_threshold) && (index_angle < angle_threshold) && (middle_angle < angle_threshold) && (ring_angle > angle_threshold) && (pink_angle > angle_threshold))
result = 2;
else if ((thumb_angle > thumb_angle_threshold) && (index_angle < angle_threshold) && (middle_angle < angle_threshold) && (ring_angle < angle_threshold) && (pink_angle > angle_threshold))
result = 3;
else if ((thumb_angle > thumb_angle_threshold) && (index_angle < angle_threshold) && (middle_angle < angle_threshold) && (ring_angle < angle_threshold) && (pink_angle < angle_threshold))
result = 4;
else if ((thumb_angle < thumb_angle_threshold) && (index_angle < angle_threshold) && (middle_angle < angle_threshold) && (ring_angle < angle_threshold) && (pink_angle < angle_threshold))
result = 5;
else if ((thumb_angle < thumb_angle_threshold) && (index_angle > angle_threshold) && (middle_angle > angle_threshold) && (ring_angle > angle_threshold) && (pink_angle < angle_threshold))
result = 6;
else if ((thumb_angle < thumb_angle_threshold) && (index_angle > angle_threshold) && (middle_angle > angle_threshold) && (ring_angle > angle_threshold) && (pink_angle > angle_threshold))
result = 7;
else if ((thumb_angle > 5) && (index_angle > angle_threshold) && (middle_angle < angle_threshold) && (ring_angle < angle_threshold) && (pink_angle < angle_threshold))
result = 8;
else
result = -1;
return result;
}
private float Vector2DAngle(float x1,float y1,float x2,float y2)
{
double PI = 3.141592653;
double t2 = Math.sqrt(Math.pow(x1, 2) + Math.pow(y1, 2)) * Math.sqrt(Math.pow(x2, 2) + Math.pow(y2, 2));
double t = (x1 * x2 + y1 * y2) /t2 ;
double angle = Math.acos(t) * (180 / Math.PI);
Double D = Double.valueOf(angle);
float f = D.floatValue();
return f;
}
private String GetGestureResult(int result)
{
String result_str = "无";
switch (result)
{
case 1:
result_str = "One";
break;
case 2:
result_str = "Two";
break;
case 3:
result_str = "Three";
break;
case 4:
result_str = "Four";
break;
case 5:
result_str = "Five";
break;
case 6:
result_str = "Six";
break;
case 7:
result_str = "ThumbUp";
break;
case 8:
result_str = "Ok";
break;
case 9:
result_str = "Fist";
break;
default:
break;
}
return result_str;
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/buttonBarStyle" android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/button_change_camera"
android:layout_width="wrap_content"
style="?android:attr/buttonBarButtonStyle" android:layout_height="wrap_content"
android:text="切换摄像头" />
<Button
android:id="@+id/button_change_picture_show"
android:layout_width="wrap_content"
style="?android:attr/buttonBarButtonStyle" android:layout_height="wrap_content"
android:text="备用" />
</LinearLayout>
<FrameLayout
android:id="@+id/texts"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/showtext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="手势结果" />
</FrameLayout>
<FrameLayout
android:id="@+id/preview_display_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/no_camera_access_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="相机连接失败" />
</FrameLayout>
</LinearLayout>
3:后续如需可以上传DEMO工程