基于Android+OpenCV+CNN+Keras的智能手语数字实时翻译——深度学习算法应用(含Python、ipynb工程源码)+数据集(四)

news2024/11/28 12:40:14

目录

  • 前言
  • 总体设计
    • 系统整体结构图
    • 系统流程图
  • 运行环境
  • 模块实现
    • 1. 数据预处理
    • 2. 数据增强
    • 3. 模型构建
    • 4. 模型训练及保存
    • 5. 模型评估
    • 6. 模型测试
      • 1)权限注册
      • 2)模型导入
      • 3)总体模型构建
      • 4)处理视频中的预览帧数据
      • 5)处理图片数据
      • 6)多页面设置
      • 7)布局文件代码
  • 相关其它博客
  • 工程源代码下载
  • 其它资料下载


在这里插入图片描述

前言

本项目依赖于Keras深度学习模型,旨在对手语进行分类和实时识别。为了实现这一目标,项目结合了OpenCV库的相关算法,用于捕捉手部的位置,从而能够对视频流和图像中的手语进行实时识别。

首先,项目使用OpenCV库中的算法来捕捉视频流或图像中的手部位置。这可以涉及到肤色检测、运动检测或者手势检测等技术,以精确定位手语手势。

接下来,项目利用CNN深度学习模型,对捕捉到的手语进行分类,经过训练,能够将不同的手语手势识别为特定的类别或字符。

在实时识别过程中,视频流或图像中的手语手势会传递给CNN深度学习模型,模型会进行推断并将手势识别为相应的类别。这使得系统能够实时地识别手语手势并将其转化为文本或其他形式的输出。

总的来说,本项目结合了计算机视觉和深度学习技术,为手语识别提供了一个实时的解决方案。这对于听觉障碍者和手语使用者来说是一个有益的工具,可以帮助他们与其他人更轻松地进行交流和理解。

总体设计

本部分包括系统整体结构图和系统流程图。

系统整体结构图

系统整体结构如图所示。

在这里插入图片描述

系统流程图

系统流程如图所示。

在这里插入图片描述

运行环境

本部分包括 Python 环境、TensorFlow环境、 Keras环境和Android环境。

模块实现

本项目包括6个模块:数据预处理、数据增强、模型构建、模型训练及保存、模型评估和模型测试,下面分别介绍各模块的功能及相关代码。

1. 数据预处理

在Kaggle上下载相应的数据集,下载地址为https://www.kaggle.com/ardamavi/sign-language-digits-dataset。

详见博客。

2. 数据增强

为方便展示生成图片的效果及对参数进行微调,本项目未使用keras直接训练生成器,而是先生成一个增强过后的数据集,再应用于模型训练。

详见博客。

3. 模型构建

数据加载进模型之后,需要定义模型结构,并优化损失函数。

详见博客。

4. 模型训练及保存

本部分包括模型训练和模型保存的相关代码。

详见博客。

5. 模型评估

由于网络上缺乏手语识别相关模型,为方便在多种模型中选择最优模型,以及进行模型的调优,模型应用于安卓工程之前,需要先在PC设备上使用Python文件进行初步的运行测试,以便验证本方案的手语识别策略是否可行并选择最优的分类模型。

详见博客。

6. 模型测试

评估整体模型可行性后,将手语识别模型应用于Android Studio工程中,完成APP。具体步骤如下。

1)权限注册

首先,为分别实现在视频和相片中识别手语,注册两个activity活动;其次,实现调用摄像头,在AndroidManifest.xml中进行摄像头权限及存储权限的申请。为了访问SD卡,注册FileProvider; 最后规定程序入口以及启动方式。

相关代码如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.cameraapp">
    <uses-permission android:name="android.permission.CAMERA" />
  	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-feature android:name="android.hardware.camera" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity"
            android:screenOrientation="landscape">
            android:label="手语实时识别">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="com.example.cameraapp.Second"
            android:label="图片识别">
            <intent-filter>
                <action android:name="com.litreily.SecondActivity"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.example.cameraapp.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
    </application>
</manifest>

2)模型导入

模型导入相关操作如下。
(1) 将训练好的.pb文件放入app/src/main/assets下。若不存在assets目录,则右击菜单项main→new→Directory,输入assets创建目录。

(2) 新建类PredictionTF.java,在该类中加载so库,调用TensorFlow模型得到预测结果。

(3)在MainActivity.java中声明模型存放路径,建立PredictTF对象,调用PredictionTF类,并输入相应类进行应用。相关代码如下:

//加载模型
String MODEL_FILE = "file:///android_asset/trained_model_imageDataGenerator.pb";  //模型地址
PredictTF tf =new PredictTF(getAssets(),MODEL_FILE);

(4) 在MainActivity.java的onCreate()方法中中加载OpenCV库,相关代码如下:

//加载OpenCV库
private void staticLoadCVLibraries(){
    boolean load = OpenCVLoader.initDebug();
    if(load) {
        Log.i("CV", "Open CV Libraries loaded...");
    }
}

3)总体模型构建

在PredictTF内构建预测所需的相应函数,具体步骤如下。

(1)加载so库并声明所需的属性,相关代码如下:

private static final String TAG = "PredictTF";
TensorFlowInferenceInterface tf;
static {  //加载libtensorflow_inference.so库文件
    System.loadLibrary("tensorflow_inference");
    Log.e(TAG, "libtensorflow_inference.so库加载成功");
}
//PATH TO OUR MODEL FILE AND NAMES OF THE INPUT AND OUTPUT NODES
//各节点名称
private String INPUT_NAME = "conv2d_1_input";
private String OUTPUT_NAME = "output_1";
float[] PREDICTIONS = new float[10];   //手语分类模型的输出
private int[] INPUT_SIZE = {64, 64, 1};   //模型的输入形状

(2)使用OpenCV进行手部位置的捕捉

将不符合肤色检测阈值的区域涂黑,相关代码如下:

      //肤色识别
private Mat skin(Mat frame) {
    int iLowH = 0;
    int iHighH = 20;
    int iLowS = 40;
    int iHighS = 255;
    int iLowV = 80;
    int iHighV = 255;
    Mat hsv=new Mat();
    Imgproc.cvtColor(frame,hsv,Imgproc.COLOR_RGBA2BGR);  //将输入图片转为灰度图
    Imgproc.cvtColor(hsv,hsv,Imgproc.COLOR_BGR2HSV);
    Mat skinMask=new Mat();
    Core.inRange(hsv, new Scalar(iLowH, iLowS, iLowV), new Scalar(iHighH, iHighS, iHighV),skinMask);
    Imgproc.GaussianBlur(skinMask,skinMask,new Size(5,5),0);  //高斯滤波
    Mat skin=new Mat();
    bitwise_and(frame,frame,skin,skinMask);  //将不符合肤色阈值的区域涂黑
    return skin;
}

在predict函数中将提取到的区域进行高斯滤波以及二值化,并使用findContour函数进行轮廓提取。对比轮廓大小,忽略面积小于阈值的连通域。最后使用boundingRect函数提取原图。

相关代码如下:

//载入位图并转为openCV的Mat对象
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
Mat frame = new Mat();
if (imagePath == null) {System.out.print("imagePath is null");}
Utils.bitmapToMat(bitmap, frame);
Core.flip(frame,frame,1);   //水平镜像翻转
//图片预处理
frame=skin(frame);          //肤色识别
//Mat frame1=new Mat();
Imgproc.cvtColor(frame,frame,Imgproc.COLOR_BGR2GRAY);       //转化为灰度图
Mat frame1=new Mat();
Imgproc.GaussianBlur(frame,frame1,new Size(5,5),0);      //高斯滤波
double res=Imgproc.threshold(frame1, frame1,50, 255, Imgproc.THRESH_BINARY);      //二值化
//Imgproc.cvtColor(frame,frame,);
//提取所有轮廓
List<MatOfPoint> contours=new ArrayList<MatOfPoint>();
Mat hierarchy = new Mat();
Imgproc.findContours(frame1, contours, hierarchy, 
Imgproc.RETR_EXTERNAL, 
Imgproc.CHAIN_APPROX_SIMPLE);       //轮廓提取
//找到最大区域并填充
int max_idx= 0;
List<Double> area=new ArrayList<>();
for (int i=0;i < contours.size();i++) {
    area.add(Imgproc.contourArea((contours).get(i)));
    //max_idx = area.indexOf(Collections.max(area));
}
max_idx = area.indexOf(Collections.max(area));
//得到矩形
Rect rect= Imgproc.boundingRect(contours.get(max_idx));
//提取相关区域
String mess;
mess= String.valueOf(frame.channels());
Log.i("CV", "the type of frame is:"+mess);
Mat chepai_raw = new Mat(frame,rect);    //提取相关区域
Mat cheapi=new Mat();
Core.flip(chepai_raw,cheapi,1);       //水平镜面反转

(3)将OpenCV提取到的区域输入至手语分类模型中

将提取到的bitmpa位图转换成一维数组以供模型输入,相关代码如下:

Bitmap input = Bitmap.createBitmap(cheapi.cols(), 
cheapi.rows(), 
Bitmap.Config.ARGB_8888);
Utils.matToBitmap(cheapi,input);
float[] input_data=bitmapToFloatArray(input,64,64);

Bitmap.createBitmap()函数定义相关代码如下:

    //将bitmap转化为一维浮点数组
   //本函数修改自https://blog.csdn.net/chaofeili/article/details/89374324
   public static float[] bitmapToFloatArray(Bitmap bitmap,int rx, int ry){
        int height = bitmap.getHeight();
        int width = bitmap.getWidth();
        //计算缩放比例
        float scaleWidth = ((float) rx) / width;
        float scaleHeight = ((float) ry) / height;
        Matrix matrix = new Matrix();
        matrix.postScale(scaleWidth, scaleHeight);
        bitmap=Bitmap.createBitmap(bitmap,0,0,width,height, matrix, true);
        height = bitmap.getHeight();
        width = bitmap.getWidth();
        float[] result = new float[height*width];
        int k = 0;
        //行优先
        for(int j = 0;j < height;j++){
            for (int i = 0;i < width;i++){
                int argb = bitmap.getPixel(i,j);
                int r = Color.red(argb);
                int g = Color.green(argb);
                int b = Color.blue(argb);
                int a = Color.alpha(argb);
                //由于是灰度图,所以r,g,b分量是相等的
                assert(r==g && g==b);
//Log.i(TAG,i+","+j+":argb = "+argb+", a="+a+", r="+r+", g="+g+", b="+b);
                result[k++] = r / 255.0f;
            }
        }
        return result;
    }

由于原数据集存在标签错误的情况,需要为标签纠错提供正确的映射字典,相关代码如下:

Map<Integer, Integer> label_map=new HashMap<Integer, Integer>();
label_map.put(0,9);label_map.put(1,0);label_map.put(2,7);
label_map.put(3,6);label_map.put(4,1);
label_map.put(5,8);label_map.put(6,4);
label_map.put(7,3);label_map.put(8,2);label_map.put(9,5);     //标签纠错

开始应用模型并得到预测的结果,相关代码如下:

    //开始应用模型
tf = new TensorFlowInferenceInterface(getAssets(),MODEL_PATH);   //加载模型
tf.feed(INPUT_NAME, input_data, 1, 64, 64, 1);
tf.run(new String[]{OUTPUT_NAME});
//copy the output into the PREDICTIONS array
tf.fetch(OUTPUT_NAME,PREDICTIONS);
//Obtained highest prediction
Object[] results = argmax(PREDICTIONS);  //找到预测置信度最大的类作为分类结果
int class_index = (Integer) results[0];
float confidence = (Float) results[1];
/*try{
    final String conf = String.valueOf(confidence * 100).substring(0,5);
    //Convert predicted class index into actual label name
    final String label = ImageUtils.getLabel(getAssets().open("labels.json"),class_index);
    //Display result on UI
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            resultView.setText(label + " : " + conf + "%");
        }
    });
} catch (Exception e){
}//*/
int label=label_map.get(class_index);         //标签纠错
results [0]=label;

预测函数相关代码如下:

private Object[] perdict (Bitmap bitmap){
    //载入位图并转为openCV的Mat对象
    //Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
    Mat frame = new Mat();
    //if (imagePath == null) {System.out.print("imagePath is null");}
    Utils.bitmapToMat(bitmap, frame);
    //Core.flip(frame,frame,1);   //水平镜像翻转
    //图片预处理
    Mat frame2=new Mat();
    frame2=skin(frame);          //肤色识别
    Imgproc.cvtColor(frame2,frame2,Imgproc.COLOR_BGR2GRAY);  //转化为灰度图
    Mat frame1=new Mat();
    Imgproc.GaussianBlur(frame2,frame1,new Size(5,5),0);      //高斯滤波
    double res=Imgproc.threshold(frame1, frame1,50, 255, Imgproc.THRESH_BINARY);      //二值化
    //提取所有轮廓
    List<MatOfPoint> contours=new ArrayList<MatOfPoint>();
    Mat hierarchy = new Mat();
    Imgproc.findContours(frame1, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);       //轮廓提取
    //找到最大区域并填充
    int max_idx= 0;
    List<Double> area=new ArrayList<>();
    for (int i=0;i < contours.size();i++) {
        area.add(Imgproc.contourArea((contours).get(i)));
       }
    max_idx = area.indexOf(Collections.max(area));
    //得到矩形
    Rect rect= Imgproc.boundingRect(contours.get(max_idx));
    //提取相关区域
    String mess;
    Imgproc.cvtColor(frame,frame,Imgproc.COLOR_BGR2GRAY);//将原图转化为灰度图
    mess= String.valueOf(frame.channels());
    Log.i("CV", "the type of frame is:"+mess);
    Mat chepai_raw = new Mat(frame,rect);
    mess= String.valueOf(chepai_raw.channels());
    Log.i("CV", "the type of chepai_raw is:"+mess);
    Mat cheapi=new Mat();
    Core.flip(chepai_raw,cheapi,1);       //水平镜面反转
    //将提取的区域转为一维浮点数组输入模型
    Bitmap input = Bitmap.createBitmap(cheapi.cols(), cheapi.rows(), Bitmap.Config.ARGB_8888);
    Utils.matToBitmap(cheapi,input);
    float[] input_data=bitmapToFloatArray(input,64,64);
    Map<Integer, Integer> label_map=new HashMap<Integer, Integer>();
    //{0:9,1:0, 2:7, 3:6, 4:1, 5:8, 6:4, 7:3, 8:2, 9:5};
    label_map.put(0,9);label_map.put(1,0);label_map.put(2,7);label_map.put(3,6);label_map.put(4,1);
    label_map.put(5,8);label_map.put(6,4);label_map.put(7,3);label_map.put(8,2);label_map.put(9,5);     //标签纠错
    //开始应用模型
    tf=new TensorFlowInferenceInterface(getAssets(),MODEL_PATH);  //加载模型
    tf.feed(INPUT_NAME, input_data, 1, 64, 64, 1);
    tf.run(new String[]{OUTPUT_NAME});
    //copy the output into the PREDICTIONS array
    tf.fetch(OUTPUT_NAME,PREDICTIONS);
    //选择预测结果中置信度最大的类别
    Object[] results = argmax(PREDICTIONS);
    int class_index = (Integer) results[0];
    float confidence = (Float) results[1];
    /*try{
        final String conf = String.valueOf(confidence * 100).substring(0,5);
        //将预测的结果转化为0~9的标签
        final String label = ImageUtils.getLabel(getAssets().open("labels.json"),class_index);
        //显示结果
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                resultView.setText(label + " : " + conf + "%");
            }
        });
    } catch (Exception e){
    }
    int label=label_map.get(class_index);         //标签纠错
    results [0]=label;
    return results;
}

4)处理视频中的预览帧数据

处理预览帧是对相机预览时的每一帧数据进行处理,识别特定帧中的手语类型,具体步骤如下:

(1)新建处理预览帧所需的CameraPreview类以及ProcessWithThreadPool

(2)绑定开始预览相机视频时的事件

修改mainActivity,在新建的方法startPreview()中添加初始化相机预览的代码,最后在stopPreview()方法中停止相机预览,通过removeAllViews()将相机预览移除时,会触发CameraPreview类中的相关结束方法,关闭相机预览。相关代码如下:

public void startPreview() {
        //加载模型
StringMODEL_FILE="file:///android_asset/trained_model_imageDataGenerator.pb";
       //模型地址
        PredictTF tf =new PredictTF(getAssets(),MODEL_FILE);
        //新建相机预览对象
        final CameraPreview mPreview = new CameraPreview(this,tf);
        //新建可视布局
        FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
        preview.addView(mPreview);
        //调用并初始化摄像头
        SettingsFragment.passCamera(mPreview.getCameraInstance());
        PreferenceManager.setDefaultValues(this, R.xml.preferences, false);  SettingsFragment.setDefault(PreferenceManager.getDefaultSharedPreferences(this));        SettingsFragment.init(PreferenceManager.getDefaultSharedPreferences(this));
        //设置开始按钮监听器
        Button buttonSettings = (Button) findViewById(R.id.button_settings);
        buttonSettings.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
getFragmentManager().beginTransaction().replace(R.id.camera_preview, new SettingsFragment()).addToBackStack(null).commit();
            }
        });
    }
public void stopPreview() {
    //移除相机预览界面
    FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
    preview.removeAllViews();
}

在mainActivity中,onCreate()方法中使用两个按钮绑定上述方法,相关代码如下:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    //加载OpenCV库
    staticLoadCVLibraries();  
    //绑定相机预览开始按钮
    Button buttonStartPreview = (Button) findViewById(R.id.button_start_preview);
    buttonStartPreview.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            startPreview();
        }
    });
    //绑定相机预览停止按钮
    Button buttonStopPreview = (Button) findViewById(R.id.button_stop_preview);
    buttonStopPreview.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            stopPreview();
        }
    });
}

(3)实时处理帧数据

CameraPreview类中新增Camera.PreviewCallback接口声明,以实现此接口下的onPreviewFrame()方法,此方法用于获取每一帧的数据。相关代码如下:

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback, Camera.PreviewCallback{}

实现onPreviewFrame()方法,调用PredictTF类进行进行手语的识别,相关代码如下:

public void onPreviewFrame(byte[] data, Camera camera) {
    switch (processType) {
        case PROCESS_WITH_HANDLER_THREAD:
            processFrameHandler.obtainMessage(ProcessWithHandlerThread.WHAT_PROCESS_FRAME, data).sendToTarget();
            break;
        case PROCESS_WITH_QUEUE:
            try {
                frameQueue.put(data);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            break;
        case PROCESS_WITH_ASYNC_TASK:
            new ProcessWithAsyncTask().execute(data);
            break;
        case PROCESS_WITH_THREAD_POOL:
            processFrameThreadPool.post(data);
            try {
                //延时3s处理
                Thread.sleep(500);  //手掌位置捕捉的延时
                NV21ToBitmap transform = new NV21ToBitmap(getContext().getApplicationContext());
                Bitmap bitmap= transform.nv21ToBitmap(data,1920,1080);
                String num;
                Object[] results=tf.perdict(bitmap);
                if (results!=null) {
                    num = "result:" + results[0].toString() + "  confidence:" + results[1].toString();
                    Toast.makeText(getContext().getApplicationContext(), num, Toast.LENGTH_SHORT).show();
                    Thread.sleep(3000);  //如果监测到有效手语,则进行延时
                }
             } catch (InterruptedException e) {
                e.printStackTrace();
            }
            break;
        default:
            throw new IllegalStateException("Unexpected value: " + processType);
             //*/
    }
}

5)处理图片数据

本部分包括调用摄像头获取图片、从相册中选择图片、对图片进行预测和显示。

(1)调用摄像头获取图片。
file对象用于存放摄像:头拍摄的照片,存放在当前应用缓存数据的位置,并调用getUriForFile()方法获取此目录。

为了兼容低版本,随后进行判断:如果系统版本低于Android7.0,则调用Uri的formFlie ()方法将File对象转为Uri对象;否则调用FileProvider对象的getUriForFile ()方法将File对象转换成Uri对象。

构建Intent对象,将action指定为android.media.action.IMAGE_CAPTURE,再调用putExtra ()方法指定图片的输出地址,调用startActivityForResul ()方法打开相机程序,成功后返回TAKE_PHOTO进行下一步处理。相关代码如下:

//创建File对象,用于存储拍照后的图片
    File outputImage = new File(getExternalCacheDir(), "output_image.jpg");
    try {
        if (outputImage.exists()) {
            outputImage.delete();
        }
        outputImage.createNewFile();
    } catch (IOException e) {
        e.printStackTrace();
    }
    if (Build.VERSION.SDK_INT < 24) {
        imageUri = Uri.fromFile(outputImage);
    } else {
        imageUri=FileProvider.getUriForFile(Second.this,"com.example.cameraapp.fileprovider", outputImage);
    }
    //启动相机程序
    Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
    intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
    startActivityForResult(intent, TAKE_PHOTO);
}

新增file_paths.xml文件,相关代码如下:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="my_images"  path="/" />
</paths>

Androidfest.xml文件中注册fileProvider,相关代码如下:

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.example.cameraapp.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

(2) 从相册中选择图片

AndroidManifest.xml文件中申请WRITE_EXTERNAL_STORAGE权限,因为从相册中选择图片需要访问到手机的SD卡数据。

创建Intent对象,将action指定为android.intent.action.GET_CONTENT,调用startActivityForResult() 方法打开手机相册,成功后返回CHOOSE_PHOTO用于下一步处理。

相关代码如下:

private void openAlbum() {
    Intent intent = new Intent("android.intent.action.GET_CONTENT");
    intent.setType("image/*");
    startActivityForResult(intent, CHOOSE_PHOTO); // 打开相册
}

为了兼容低版本,用handleImageOnKitKat()handleImageBeforeKitKat()函数分别对4.4以上及4.4版本以下手机的图片进行处理。

handleImageOnKitKat()是解析之前封装好的Uri对象,而handleImageBeforeKitKat()的Uri由于未进行封装,直接将Uri对象传入getImagePath()即可。相关代码如下:

private void handleImageOnKitKat(Intent data) {
    String imagePath = null;
    Uri uri = data.getData();
    Log.d("TAG", "handleImageOnKitKat: uri is " + uri);
    if (DocumentsContract.isDocumentUri(this, uri)) {
        // 如果是document类型的Uri,则通过document ID处理
        String docId = DocumentsContract.getDocumentId(uri);
        if("com.android.providers.media.documents".equals(uri.getAuthority())) {
            String id = docId.split(":")[1]; //解析数字格式的ID
            String selection = MediaStore.Images.Media._ID + "=" + id;
            imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
        }
 else 
if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {
            Uri contentUri=ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));
            imagePath = getImagePath(contentUri, null);
        }
    } else if ("content".equalsIgnoreCase(uri.getScheme())) {
        //如果是content类型的Uri,则使用普通方式处理
        imagePath = getImagePath(uri, null);
    } else if ("file".equalsIgnoreCase(uri.getScheme())) {
        //如果是file类型的Uri,直接获取图片路径即可
        imagePath = uri.getPath();
    }
    displayImage(imagePath); //根据图片路径显示图片
}
private void handleImageBeforeKitKat(Intent data) {
    Uri uri = data.getData();
    String imagePath = getImagePath(uri, null);
    displayImage(imagePath);
}

(3) 对图片进行预测和显示

使用PredictTF对象相关方法预测出从相机或相册中获取的图片位图对象中的手势类型,并使用setImageBitmap函数显示。

相关代码如下:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode,resultCode, data);
   switch (requestCode)
 {
        case TAKE_PHOTO:
            if (resultCode == RESULT_OK) {
                try {  //将拍摄的照片显示出来并进行识别
Bitmapbitmap=BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
                    //识别图片
                    Object[] results=tf.perdict(bitmap);
                    String num;
                    num ="result:"+results[0].toString()+"  confidence:"+results[1].toString();
                    Toast.makeText(Second.this, num , Toast.LENGTH_SHORT).show();
                    //展示图片
                    picture.setImageBitmap(bitmap);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            break;
        case CHOOSE_PHOTO:
            if (resultCode == RESULT_OK) {
                //判断手机系统版本号
                if (Build.VERSION.SDK_INT >= 19) {
                    //4.4及以上系统使用这个方法处理图片
                    handleImageOnKitKat(data);
                } else {
                    //4.4以下系统使用这个方法处理图片
                    handleImageBeforeKitKat(data);
                }
            }
            break;
        default:
            break;
    }
}

6)多页面设置

创建完使用视频和图片进行预测的类别后,在AndroidManifest.xml文件中注册两个activity。其中在MainActivity.java中使用视频进行预测,Second.java中使 用图片进行预测。

相关代码如下:

<activity android:name=".MainActivity"
    android:screenOrientation="landscape">
    android:label="手语实时识别">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<activity
    android:name="com.example.cameraapp.Second"
    android:label="图片识别">
    <intent-filter>
        <action android:name="com.litreily.SecondActivity"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

在每个页面设置跳转按钮及监听事件,通过setClass()方法设置跳转的页面,并使用startActivityForResult()方法启动跳转。

相关代码如下:

//设置跳转按钮
Button buttonSettings = (Button) findViewById(R.id.button_settings);
buttonSettings.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v){
        Intent intent2 =new Intent(); //新建Intent对象
        intent2.setClass(MainActivity.this, Second.class);
        startActivityForResult(intent2,0);   //返回前一页
    }
});

7)布局文件代码

布局文件相关代码如下:

activity.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">
    <FrameLayout
        android:id="@+id/camera_preview"
        android:layout_width="0px"
        android:layout_height="fill_parent"
        android:layout_weight="1" />
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:orientation="vertical">
        <Button
            android:id="@+id/button_settings"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="转到图片识别" />
        <Button
            android:id="@+id/button_start_preview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="开始翻译" />
        <Button
            android:id="@+id/button_stop_preview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="停止翻译" />
        <TextView
            android:id="@+id/info1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="本次识别结果:" />
        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <TextView
            android:id="@+id/info2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="手部区域:" />
        <ImageView
            android:id="@+id/picture"
            android:layout_width="100dp"
            android:layout_height="80dp"
            android:layout_gravity="center_horizontal" />
        <TextView
            android:id="@+id/info3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="句子翻译:" />
        <TextView
            android:id="@+id/translated_statement"
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            android:singleLine="true"
            android:ellipsize="start"/>
    </LinearLayout>
</LinearLayout>
second.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <Button
        android:id="@+id/take_photo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="拍照识别" />
    <Button
        android:id="@+id/choose_from_album"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="从相册中选择" />
    <Button
        android:id="@+id/button_settings"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="转到实时翻译" />
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <ImageView
        android:id="@+id/picture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal" />
</LinearLayout>

相关其它博客

基于Android+OpenCV+CNN+Keras的智能手语数字实时翻译——深度学习算法应用(含Python、ipynb工程源码)+数据集(一)

基于Android+OpenCV+CNN+Keras的智能手语数字实时翻译——深度学习算法应用(含Python、ipynb工程源码)+数据集(二)

基于Android+OpenCV+CNN+Keras的智能手语数字实时翻译——深度学习算法应用(含Python、ipynb工程源码)+数据集(三)

基于Android+OpenCV+CNN+Keras的智能手语数字实时翻译——深度学习算法应用(含Python、ipynb工程源码)+数据集(五)

工程源代码下载

详见本人博客资源下载页


其它资料下载

如果大家想继续了解人工智能相关学习路线和知识体系,欢迎大家翻阅我的另外一篇博客《重磅 | 完备的人工智能AI 学习——基础知识学习路线,所有资料免关注免套路直接网盘下载》
这篇博客参考了Github知名开源平台,AI技术平台以及相关领域专家:Datawhale,ApacheCN,AI有道和黄海广博士等约有近100G相关资料,希望能帮助到所有小伙伴们。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1028488.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

区块链技术:解密去中心化的革命

文章目录 区块链的基础概念什么是区块链&#xff1f;区块链的核心原理1. 分布式账本2. 区块3. 加密技术4. 共识机制 区块链的工作原理区块链的交易过程区块链的安全性共识机制的作用 区块链的应用领域1. 金融服务2. 供应链管理3. 物联网4. 医疗保健5. 政府与公共服务 区块链的未…

图像练习OpenCV(01)

提取出里面最大矩形的四个顶点坐标 源图像 结果展示 代码 void getLine(std::vector<int>& data, int threshold) {for (int x 0; x < data.size(); x){if (0 data[x]){continue;}int maxValue 0, maxLoc -1, i -1;for (i x; i < data.size(); i){if …

【2023集创赛】Arm杯二等奖作品:基于Arm Cortex-M3的体感节奏音乐游戏机

本文为2023年第七届全国大学生集成电路创新创业大赛&#xff08;“集创赛”&#xff09;安谋科技杯全国二等奖作品分享&#xff0c;参加极术社区的【有奖征集】分享你的2023集创赛作品&#xff0c;秀出作品风采&#xff0c;分享2023集创赛作品扩大影响力&#xff0c;更有丰富电…

跑腿系统开发:构建实时任务分配算法的技术挑战

在跑腿系统中&#xff0c;实时任务分配算法是确保任务快速高效完成的关键因素之一。本文将介绍构建实时任务分配算法时可能面临的技术挑战&#xff0c;并提供一个简单的Python示例来解决这些挑战。 技术挑战&#xff1a; 实时数据处理&#xff1a; 跑腿系统需要处理大量的实时任…

Ganache本地测试网+cpolar内网穿透实现公网访问内网

文章目录 前言1. 本地环境服务搭建2. 局域网测试访问3. 内网穿透3.1 ubuntu本地安装cpolar内网穿透3.2 创建隧道3.3 测试公网访问 4. 配置固定二级子域名4.1 保留一个二级子域名4.2 配置二级子域名4.3 测试访问公网固定二级子域名 前言 网&#xff1a;我们通常说的是互联网&am…

K8S:Pod概念、分类及相关的策略

文章目录 一.pod相关概念&#xff11;.Pod基础概念&#xff12;.Kubrenetes集群中Pod两种使用方式&#xff13;.pause容器的Pod中的所有容器共享的资源&#xff14;.kubernetes中的pause容器主要为每个容器提供功能&#xff1a;&#xff15;.Kubernetes设计这样的Pod概念和特殊…

本地搭建CFimagehost私人图床——“cpolar内网穿透”

文章目录 1.前言2. CFImagehost网站搭建2.1 CFImagehost下载和安装2.2 CFImagehost网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar临时数据隧道3.2 Cpolar稳定隧道&#xff08;云端设置&#xff09;3.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 4.公网访问测…

7.algorithm2e中while怎么使用

algorithm2e中while怎么使用 在 algorithm2e 宏包中&#xff0c;要使用 while 循环&#xff0c;您可以使用 \While 和 \EndWhile 命令来定义循环的开始和结束。以下是如何使用 while 循环的示例&#xff1a; \documentclass{article} \usepackage[linesnumbered,boxed]{algorit…

Mac电脑音视频播放器: Infuse for Mac中文

Infuse是一款流行的多媒体播放器应用程序&#xff0c;适用于iOS、tvOS和macOS平台。它由Firecore开发&#xff0c;旨在提供出色的媒体播放体验&#xff0c;并支持广泛的视频和音频格式。 以下是Infuse的一些主要功能和特点&#xff1a; 多媒体格式支持&#xff1a;Infuse支持…

ROS2 从头开始​​:第 1 部分 — 机器人操作系统简介

火星上的机器人&#xff08;AI生成图像&#xff09; 一、说明 ROS2是机器人的朋友&#xff0c;一个他们所依赖的平台&#xff0c;用于沟通、协调和控制&#xff0c;帮助他们实现目标。ROS2以DDS为核心&#xff0c;帮助机器人探索新世界、新任务、新可能性&#xff0c;是一个方…

代码随想录--链表-反转链表

题意&#xff1a;反转一个单链表。 示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL 双指针 public class Main {public static class ListNode {int val;ListNode next;ListNode(int x) {val x;}}public ListNode reverseList(L…

涨知识,关于代码签名证书10大常见问题解答

在当今互联网时代&#xff0c;各种软件程序充斥着这个网络世界&#xff0c;大大小小的软件层出不穷&#xff0c;如何让用户信任软件并下载软件&#xff0c;是众多软件开发公司需要解决的问题&#xff0c;由此代码签名证书应运而生&#xff0c;提供了软件程序的身份认证、完整性…

uni-app获取元素具体位置获取失败

场景&#xff1a;想要通过链接跳转传递catid&catid2类别id,商品类别id 跳到这一页左侧对应的类别栏上面,同时跳到右侧列表滚动到对应商品那一块区域。 遇到的问题&#xff1a;在for循环中通过绑定id获取不到商品列表的具体位置。 原因&#xff1a;在onReady函数和mounted函…

【Java】医院智能导诊系统源码:解决患者盲目就诊问题、降低患者挂错号比例

医院智能导诊系统解决患者盲目就诊问题&#xff0c;减轻分诊工作压力。降低患者挂错号比例&#xff0c;优化患者就诊流程&#xff0c;有效提高线上线下医疗机构接诊效率。患者可通过人体画像选择症状部位&#xff0c;了解对应病症信息和推荐就医科室。 一、医院智能导诊系统概述…

科锐逆向第二阶段(一)SDK

基本概念 什么是 SDK SDK 是软件开发工具包&#xff08;Software Development Kit&#xff09;的缩写。它是一个集成了软件开发所需工具、库文件、示例代码和文档等资源的软件包。 SDK 通常由软件开发公司或平台提供&#xff0c;旨在帮助开发人员构建、测试和部署特定类型的…

Offset Explorer(Kafka消息可视化工具)报invalid hex digit ‘{‘错误解决方法

解决办法&#xff1a; 根据代码的实际情况&#xff0c;设置成对应的值。设置完成后点update、refresh更新。

excel 通过SUMIF关键词统计词频

经常会对句子中含有的某些词汇数量进行统计&#xff0c;excel 也可以实现初级的操作 比如有如下文本 想要统计旅游和好两个词在这些文本中出现了多少次 用如下函数即可 SUMIF(A:A,"*"&C2&"*",B:B) 可以很方便的统计出好出现了3次数据&#xff0…

Python爬虫有哪些库,分别怎么用

目录 Python常用爬虫库 代码示例 requests BeautifulSoup Scrapy Selenium PyQuery Axios requests-html pyppeteer 总结 Python是一种非常流行的编程语言&#xff0c;因其易学易用和广泛的应用而受到开发者的喜爱。在Python中&#xff0c;有许多库可以用于爬虫程序…

“探索前后端分离架构下的Vue.js应用开发“

目录 引言1. 前后端分离2. Vue的简介1. Vue.js是什么&#xff1f;2. 库和框架的区别3. MVVM的介绍 3. Vue的入门数据的双向绑定数据的单项绑定 4. Vue的生命周期总结 引言 在当今互联网时代&#xff0c;前后端分离架构已经成为了Web应用开发的主流趋势。前后端分离架构的核心思…

【二叉树-02】二叉树的最近公共祖先-力扣 236 题

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…