【Android +Tensroflow Lite】实现从基于机器学习语音中识别指令讲解及实战(超详细 附源码)

news2025/1/18 6:59:24

需要源码和配置文件请点赞关注收藏后评论区留言~~~

一、基于机器学习的语音推断

Tensorflow基于分层和模块化的设计思想,整个框架以C语言的编程接口为界,分为前端和后端两大部分 Tensorflow框架结构如下图

 二、Tensorflow Lite简介

虽然Tensorflow是一款十分优秀的机器学习框架,但是它层次众多,不适合在单个设备上独立运行,为此Google推出了Tensorflow Lite,也就是Tensorflow的精简版,它可以在移动设备,嵌入式设备和物联网设备上运行Tensorflow模型

Tensorflow Lite包括下列两个主要组件

Tensorflow Lite解释器 允许在设备端的不同硬件上运行优化过的模型

Tensorflow Lite转换器 将Tensorflow模型转换为解释器使用的格式 同时通过优化提高应用性能

Tensorflow Lite允许在网络边缘的设备上执行机器学习任务,无须在设备与服务器之间来回发送数据 对开发者来说 在设备端执行机器学习任务有以下好处

缩短延迟 数组无须往返服务器

保护隐私 任何数据都不会离开设备

减少连接 不需要互联网连接

降低功耗 网络连接非常耗电 

三、从语音中识别指令实战 

首先给App工程手工添加Tensorflow Lite支持

implementation 'org.tensorflow:tensorflow-lite:2.5.0'

同时还要引入语音识别的配置文件 请点赞关注收藏后评论区留言私信博主

然后在活动代码中初始化Tensorflow Lite 分别读取标签配置 加载模型文件 

运行效果如下

语音识别支持App支持识别英文单词指令 识别到的指令会高亮显示在App界面 并且标出吻合度

需要对着手机大声朗读上述英文单词 就可观察到语音推断结果

所以此处连接真机测试效果更好 模拟机不好录制语音~~~

 

 

 四、代码

部分源码如下 需要全部代码请点赞关注收藏后评论区留言~~~

package com.example.voice;

import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.example.voice.adapter.WordRecyclerAdapter;
import com.example.voice.bean.WordInfo;
import com.example.voice.tensorflow.RecognizeCommands;

import org.tensorflow.lite.Interpreter;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

public class VoiceInferenceActivity extends AppCompatActivity {
    private final static String TAG = "VoiceInferenceActivity";
    private TextView tv_cost; // 声明一个文本视图对象
    private WordRecyclerAdapter mAdapter; // 英语单词的循环适配器
    private String[] mWordArray = new String[]{"Yes", "No", "Up", "Down", "Left", "Right", "On", "Off", "Stop", "Go"};
    private List<WordInfo> mWordList = new ArrayList<>(); // 单词信息列表

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_voice_inference);
        initView(); // 初始化视图
        initTensorflow(); // 初始化Tensorflow
    }

    // 初始化视图
    private void initView() {
        TextView tv_rate = findViewById(R.id.tv_rate);
        tv_rate.setText(SAMPLE_RATE + " Hz");
        tv_cost = findViewById(R.id.tv_cost);
        for (String word : mWordArray) {
            mWordList.add(new WordInfo(word, null));
        }
        RecyclerView rv_word = findViewById(R.id.rv_word);
        GridLayoutManager manager = new GridLayoutManager(this, 2);
        rv_word.setLayoutManager(manager);
        mAdapter = new WordRecyclerAdapter(this, mWordList);
        rv_word.setAdapter(mAdapter);
    }

    private static final int SAMPLE_RATE = 16000;
    private static final int SAMPLE_DURATION_MS = 1000;
    private static final int RECORDING_LENGTH = (int) (SAMPLE_RATE * SAMPLE_DURATION_MS / 1000);
    private static final long AVERAGE_WINDOW_DURATION_MS = 1000;
    private static final float DETECTION_THRESHOLD = 0.50f;
    private static final int SUPPRESSION_MS = 1500;
    private static final int MINIMUM_COUNT = 3;
    private static final long MINIMUM_TIME_BETWEEN_SAMPLES_MS = 30;
    private static final String LABEL_FILENAME = "conv_actions_labels.txt";
    private static final String MODEL_FILENAME = "conv_actions_frozen.tflite";
    // Working variables.
    private short[] recordBuffer = new short[RECORDING_LENGTH];
    private int recordOffset = 0;
    private boolean continueRecord = true;
    private Thread recordThread;
    private boolean continueRecognize = true;
    private Thread recognizeThread;
    private final ReentrantLock recordBufferLock = new ReentrantLock();

    private List<String> labelList = new ArrayList<>(); // 指令标签列表
    private RecognizeCommands recognizeCommands = null; // 待识别的指令
    private Interpreter.Options tfLiteOptions = new Interpreter.Options(); // 解释器选项
    private Interpreter tfLite; // Tensorflow Lite的解释器
    private long costTime; // 每次语音识别的耗费时间

    // 初始化Tensorflow
    private void initTensorflow() {
        Log.d(TAG, "Reading labels from: " + LABEL_FILENAME);
        try (BufferedReader br = new BufferedReader(new InputStreamReader(getAssets().open(LABEL_FILENAME)))) {
            String line;
            while ((line = br.readLine()) != null) {
                labelList.add(line);
            }
        } catch (Exception e) {
            throw new RuntimeException("Problem reading label file!", e);
        }
        Log.d(TAG, "labelList.size()=" + labelList.size());

        // 设置一个对象来平滑识别结果,以提高准确率
        recognizeCommands = new RecognizeCommands(
                labelList,
                AVERAGE_WINDOW_DURATION_MS,
                DETECTION_THRESHOLD,
                SUPPRESSION_MS,
                MINIMUM_COUNT,
                MINIMUM_TIME_BETWEEN_SAMPLES_MS);

        try {
            MappedByteBuffer tfLiteModel = loadModelFile(getAssets(), MODEL_FILENAME);
            tfLite = new Interpreter(tfLiteModel, tfLiteOptions);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        tfLite.resizeInput(0, new int[]{RECORDING_LENGTH, 1});
        tfLite.resizeInput(1, new int[]{1});

        startRecord(); // 开始录音
        startRecognize(); // 开始识别
    }

    private MappedByteBuffer loadModelFile(AssetManager assets, String modelFilename) throws Exception {
        Log.d(TAG, "modelFilename="+modelFilename);
        AssetFileDescriptor descriptor = assets.openFd(modelFilename);
        FileInputStream fis = new FileInputStream(descriptor.getFileDescriptor());
        FileChannel fileChannel = fis.getChannel();
        long startOffset = descriptor.getStartOffset();
        long declaredLength = descriptor.getDeclaredLength();
        return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength);
    }

    // 开始录音
    public synchronized void startRecord() {
        if (recordThread != null) {
            return;
        }
        continueRecord = true;
        recordThread = new Thread(() -> record());
        recordThread.start();
    }

    // 停止录音
    public synchronized void stopRecord() {
        if (recordThread == null) {
            return;
        }
        continueRecord = false;
        recordThread = null;
    }

    // 录制音频
    private void record() {
        android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
        // Estimate the buffer size we'll need for this device.
        int bufferSize = AudioRecord.getMinBufferSize(
                SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
        if (bufferSize == AudioRecord.ERROR || bufferSize == AudioRecord.ERROR_BAD_VALUE) {
            bufferSize = SAMPLE_RATE * 2;
        }
        short[] audioBuffer = new short[bufferSize / 2];

        AudioRecord record = new AudioRecord(
                MediaRecorder.AudioSource.DEFAULT,
                SAMPLE_RATE,
                AudioFormat.CHANNEL_IN_MONO,
                AudioFormat.ENCODING_PCM_16BIT,
                bufferSize);
        if (record.getState() != AudioRecord.STATE_INITIALIZED) {
            Log.e(TAG, "Audio Record can't initialize!");
            return;
        }
        record.startRecording();

        Log.d(TAG, "Start record");
        // Loop, gathering audio data and copying it to a round-robin buffer.
        while (continueRecord) {
            int numberRead = record.read(audioBuffer, 0, audioBuffer.length);
            int maxLength = recordBuffer.length;
            int newRecordOffset = recordOffset + numberRead;
            int secondCopyLength = Math.max(0, newRecordOffset - maxLength);
            int firstCopyLength = numberRead - secondCopyLength;
            // We store off all the data for the recognition thread to access. The ML
            // thread will copy out of this buffer into its own, while holding the
            // lock, so this should be thread safe.
            recordBufferLock.lock();
            try {
                System.arraycopy(audioBuffer, 0, recordBuffer, recordOffset, firstCopyLength);
                System.arraycopy(audioBuffer, firstCopyLength, recordBuffer, 0, secondCopyLength);
                recordOffset = newRecordOffset % maxLength;
            } finally {
                recordBufferLock.unlock();
            }
        }

        record.stop();
        record.release();
    }

    // 开始识别
    public synchronized void startRecognize() {
        if (recognizeThread != null) {
            return;
        }
        continueRecognize = true;
        recognizeThread = new Thread(() -> recognize());
        recognizeThread.start();
    }

    // 停止识别
    public synchronized void stopRecognize() {
        if (recognizeThread == null) {
            return;
        }
        continueRecognize = false;
        recognizeThread = null;
    }

    // 识别语音
    private void recognize() {
        Log.d(TAG, "Start recognition");
        short[] inputBuffer = new short[RECORDING_LENGTH];
        float[][] floatInputBuffer = new float[RECORDING_LENGTH][1];
        float[][] outputScores = new float[1][labelList.size()];
        int[] sampleRateList = new int[]{SAMPLE_RATE};

        // Loop, grabbing recorded data and running the recognition model on it.
        while (continueRecognize) {
            long startTime = System.currentTimeMillis();
            // The record thread places data in this round-robin buffer, so lock to
            // make sure there's no writing happening and then copy it to our own
            // local version.
            recordBufferLock.lock();
            try {
                int maxLength = recordBuffer.length;
                int firstCopyLength = maxLength - recordOffset;
                int secondCopyLength = recordOffset;
                System.arraycopy(recordBuffer, recordOffset, inputBuffer, 0, firstCopyLength);
                System.arraycopy(recordBuffer, 0, inputBuffer, firstCopyLength, secondCopyLength);
            } finally {
                recordBufferLock.unlock();
            }

            // We need to feed in float values between -1.0f and 1.0f, so divide the
            // signed 16-bit inputs.
            for (int i = 0; i < RECORDING_LENGTH; ++i) {
                floatInputBuffer[i][0] = inputBuffer[i] / 32767.0f;
            }

            Object[] inputArray = {floatInputBuffer, sampleRateList};
            Map<Integer, Object> outputMap = new HashMap<>();
            outputMap.put(0, outputScores);

            // Run the model.
            tfLite.runForMultipleInputsOutputs(inputArray, outputMap);
            // Use the smoother to figure out if we've had a real recognition event.
            final RecognizeCommands.RecognitionResult result =
                    recognizeCommands.processLatestResults(outputScores[0], System.currentTimeMillis());
            costTime = System.currentTimeMillis() - startTime;
            runOnUiThread( () -> {
                tv_cost.setText(costTime + " ms");
                // If we do have a new command, highlight the right list entry.
                if (!result.foundCommand.startsWith("_") && result.isNewCommand) {
                    int position = labelList.indexOf(result.foundCommand) - 2;
                    WordInfo word = mWordList.get(position);
                    word.percent = Math.round(result.score * 100) + "%";
                    mWordList.set(position, word);
                    mAdapter.notifyItemChanged(position);
                    new Handler(Looper.myLooper()).postDelayed(() -> {
                        word.percent = "";
                        mWordList.set(position, word);
                        mAdapter.notifyItemChanged(position);
                    }, 1500);
                }
            });
            try {
                // We don't need to run too frequently, so snooze for a bit.
                Thread.sleep(MINIMUM_TIME_BETWEEN_SAMPLES_MS);
            } catch (InterruptedException e) {
            }
        }
        Log.d(TAG, "End recognition");
    }

}

创作不易 觉得有帮助请点赞关注收藏~~~

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

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

相关文章

WMS类图结构分析-android12

为什么要分析类图&#xff1f; WMS是一个复杂的模块&#xff0c;就像一个很大的家族&#xff0c;里面有各种角色&#xff0c;认识类图就像是认识WMS模块中的各个角色&#xff0c;不先把人认清楚了&#xff0c;怎么更好的理解他们之间的交互&#xff1f; 我觉得&#xff0c;这…

【MATLAB教程案例47】基于双目相机拍摄图像的三维重建matlab仿真

欢迎订阅《FPGA学习入门100例教程》、《MATLAB学习入门100例教程》 本课程学习成果预览: 目录 1.软件版本 2.基于双目相机拍摄图像的三维重建原理概述

GII全球创新指数2013-2020

1、数据来源&#xff1a;世界知识产权组织发布的《2021年全球创新指数报告》 2、时间跨度&#xff1a;2013-2020 3、区域范围&#xff1a;全球 4、指标说明&#xff1a; 全球创新指数&#xff08;Global Innovation Index&#xff0c;GII&#xff09;是世界知识产权组织、康…

20221127-1Spring_day01(资料来自黑马程序)

Spring_day01 今日目标 掌握Spring相关概念完成IOC/DI的入门案例编写掌握IOC的相关配置与使用掌握DI的相关配置与使用 1&#xff0c;课程介绍 对于一门新技术&#xff0c;我们需要从为什么要学、学什么以及怎么学这三个方向入手来学习。那对于Spring来说: 1.1 为什么要学? …

Reactive UI -- 反应式编程UI框架入门学习(一)

反应式编程 反应式编程是一种相对于命令式的编程范式&#xff0c;由函数式的组合声明来构建异步数据流。要理解这个概念&#xff0c;可以简单的借助Excel中的单元格函数。 上图中&#xff0c;A1B1C1&#xff0c;无论B1和C1中的数据怎么变化&#xff0c;A1中的值都会自动变化&a…

Kafka - 08 Kafka Broker工作流程 | 节点服役 | 节点退役

文章目录1. Kafka Broker 工作流程2. Kafka 节点服役1. 增加一个Kafka节点2. 执行负载均衡操作3. Kafka 节点退役1. Kafka Broker 工作流程 Kafka上下线时Zookeeper中的数据变化&#xff1a; [zk: localhost:2181(CONNECTED) 9] ls / [zookeeper, kafka_cluster][zk: localhost…

使用nw.js快速开发一个基于浏览器的小型桌面端(适用于高校学生实验作业)

首先讲下退坑事项&#xff0c;节约读者时间 生成的exe会依赖SDK文件夹下的一些dll&#xff0c;所以不能简单的交付这个exe&#xff0c;需要使用额外的软件进行打包&#xff0c;如Enigma Virtual Box、inno setup自定义可执行文件的icon也要额外软件&#xff0c;如Resource Hac…

SCDM 实例教程:基本几何建模

作者 | 张杨 ANSYS SpaceClaim Direct Modeler&#xff08;简称 SCDM&#xff09;是基于直接建模思想的新一代3D建模和几何处理软件。SCDM可以显著地缩短产品设计周期&#xff0c;大幅提升CAE分析的模型处理质量和效率&#xff0c;为用户带来全新的产品设计体验。 本文将主要…

常用 CMD 命令

前言 作为一个程序员&#xff0c;可能更多的是在 Linux 中使用命令来操作。但在日常使用 Windows 的过程中&#xff0c;或多或少会使用到命令提示符窗口&#xff0c;也就是 Windows 中的 CMD。这个时候&#xff0c;掌握一些常用的命令就尤为重要了&#xff0c;一方面方便自己使…

排序-指标解读

一、ROC ROC曲线全称是(receiver operating characteristic cure)受试者工作特征曲线。 首先大家看到这里肯定会好奇&#xff0c;为啥名字这么奇怪&#xff0c;来一波背景介绍先。 “ROC起先应用于军事领域&#xff0c;据说在第二次世界大战期间&#xff0c;ROC 曲线最先是由…

几分钟快速学会Linux开启启动服务

背景 最近在银行遇到一个部署问题&#xff0c;uat、prod 两个环境的ECS中的服务要求制作好基础镜像&#xff0c;上环境的时候只需要在对应的ECS中选择更换系统即可&#xff0c;不允许传统连接SSH上去安装&#xff0c;这就要求我们就得提前把需要运行的服务内置到系统中&#x…

债券数据集:绿色债券数据集、历时新发、发行债券、DCM定价估值四大指标数据

1、绿色债券数据集 1、数据来源&#xff1a;wind 2、时间跨度&#xff1a;2016.01-2021.11年 3、区域范围&#xff1a;全国 4、指标说明&#xff1a; 部分指标如下&#xff1a; 数据截图如下&#xff1a; 2、历史新发债券数据库 1、数据来源&#xff1a;wind 2、时间跨度…

领悟《信号与系统》之 傅立叶变换的性质与应用

傅立叶变换的性质与应用一、傅里叶变换性质表二、傅里叶性质详细1. 线性性质2. 尺度变换特性3. 时移特性4. 频移特性5. 时域微分特性6. 频域微分特性7. 时域积分特性8. 频域积分特性9. 卷积定理1. 时域卷积定理2. 频域卷积定理10. 对称性11. 帕塞瓦尔定理依据傅里叶变换对概念&…

xxl-job 快速使用

xxl-job 快速使用xxl-job 简单使用注意事项执行xxl-job 简单使用 xxl-job 官方使用说明 XXL-JOB是一个分布式任务调度平台&#xff0c;其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线&#xff0c;开箱即用。 注意事项 详细可…

【Canvas】js用Canvas绘制阴阳太极图动画效果

学习JavaScript是否兴趣缺缺&#xff0c;那就需要来一个兴趣学习&#xff0c;问一下有没有兴趣用Canvas画图呢&#xff0c;可以画很多有趣的事物&#xff0c;自由发挥想象&#xff0c;收获多多哦&#xff0c;这里有一个例子&#xff0c;如何用canvas画阴阳太极图动图效果&#…

王道考研——操作系统(第二章 进程管理)(死锁)

一、死锁的概念 什么是死锁 死锁、饥饿、死循环的区别 死锁产生的必要条件 什么时候会发生死锁 死锁的处理策略 知识回顾与重要考点 二、死锁的处理策略——预防死锁 知识总览 破坏互斥条件 破坏不剥夺条件 破坏请求和保持条件 破坏循环等待条件 知识回顾与重要考点 与前面哲…

分省/市/县最低工资标准(2012-2021年)和 全国/省/市/县GDP数据(1949-2020年)

一、最低工资数据 1、数据来源&#xff1a;各省\市\县政府公布资料 2、时间跨度&#xff1a;2012-2021年 3、区域范围&#xff1a;全国各省\市\县 4、指标说明&#xff1a; 部分数据如下&#xff1a; 二、各省市县人均GDP 1、数据来源&#xff1a;地方统计局 2、时间跨度…

常用PC,移动浏览器User-Agent大全

常用PC,移动浏览器User-Agent大全,提供PC浏览器user-agent&#xff0c;Android手机浏览器user-agent大全 PC端User-Agent IE 9.0 IE 8.0 IE 7.0 IE 6.0 Firefox 4.0.1–MAC Firefox 4.0.1–Windows Opera 11.11–MAC Opera 11.11–Windows Chrome 17.0–MAC safari 5…

现在的湖仓一体像是个伪命题

文章目录开放的计算引擎SPL助力湖仓一体开放且完善的计算能力多数据源混合计算文件计算支持完善的计算能力直接访问源数据数据整理后的高性能计算SPL资料从一体机、超融合到云计算、HTAP&#xff0c;我们不断尝试将多种应用场景融合在一起并试图通过一种技术来解决一类问题&…

AWS Lambda从入门到精通

这里写自定义目录标题AWS Lambda从入门到精通介绍调用AWS Lambda的函数请求响应&#xff08;RequestResponse&#xff09;式调用采用请求响应的同步方式以函数作为应用程序的后端后端应用程序的用例和需求AWS Lambda从入门到精通 Amazon发布AWS Lambda服务&#xff0c;云计算的…