mediapipe学习-手势识别 增加简单手势识别(6)

前言

 这章增加几个简单的手势,输出结果

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>

2:测试结果
在这里插入图片描述

3:后续如需可以上传DEMO工程
下载地址

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值