如何让你的APP更智能|青训营笔记(一)

简介: 随着当下的社会发展,我们的手机屏幕越来越大。我们的单手难以覆盖整个手机,所以当我们想要单手去点击屏幕另一侧的地方的时,就会感到较为困难。这时候我们就会想,这个按钮要是更靠近我们就好了。

前言

随着当下的社会发展,我们的手机屏幕越来越大。我们的单手难以覆盖整个手机,所以当我们想要单手去点击屏幕另一侧的地方的时,就会感到较为困难。这时候我们就会想,这个按钮要是更靠近我们就好了。

那我们有办法让这些按钮自动的更靠近我们的操作手机的手么?

答案是有的,只要我们能判断出当前操作的手机是左手还是右手即可。左手按钮即可偏左;右手的话,按钮就偏右。

有了大致思路,开干!

方案

  • 训练一个二分类的CNN神经网络模型来识别用户是左手 or 右手操作。
  • 输入:用户在屏幕上的滑动轨迹
  • 输出:左手 or 右手

From: 【Android 客户端专场 学习资料二】第四届字节跳动青训营 - 掘金 (juejin.cn)

实践

样本训练这里不做介绍,对应的模型直接采用该库的 ahcyd008/OperatingHandRecognition: 端智能左右手识别学习Android Demo + 模型训练 (github.com)

导入

由于方案二事采用深度学习的,所以我们需要引入深度学习对应于Android的框架。这些框架几乎都是几个巨头大厂的,我们这边使用的是 Googletensorflowlite 版本。它是适合于 Android 使用的 tensorflow 框架,我们主要是把正常的模型压缩,转化后,就能在 Android 中使用了。

其余的两个库一个是 Googletask 库,一个是 Googleguava 库。前者是对深度学习开启后台任务以及进行监控,而 guava 则是提供一个功能更加强大的 Java 封装库。

//app/build.gradle
dependencies {
      // Task API
    implementation "com.google.android.gms:play-services-tasks:17.2.1"
    // tensorflow lite 依赖
    implementation 'org.tensorflow:tensorflow-lite:0.0.0-nightly-SNAPSHOT'
    implementation("com.google.guava:guava:31.1-android")
}
复制代码
//settings.gradle
pluginManagement {
    repositories {
        gradlePluginPortal()
        google()
        mavenCentral()
        maven { url "https://jitpack.io" }
        maven {
            name 'ossrh-snapshot'
            url 'https://oss.sonatype.org/content/repositories/snapshots'
        }
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        jcenter()
        maven {
            name 'ossrh-snapshot'
            url 'https://oss.sonatype.org/content/repositories/snapshots'
        }
    }
}
复制代码

导入库的代码如上,记得在最后要加入 tensorflow 的仓库地址。

1.webp.jpg

最后记得引入项目打包过的模型

模型的连接处理

class OperatingHandClassifier(private val context: Context) {
    private var interpreter: Interpreter? = null
    private var modelInputSize = 0
    var isInitialized = false
        private set
    /** Executor to run inference task in the background */
    private val executorService: ExecutorService = Executors.newSingleThreadScheduledExecutor()
    private var hasInit = false
    fun checkAndInit() {
        if (hasInit) {
            return
        }
        hasInit = true
        val task = TaskCompletionSource<Void?>()
        executorService.execute {
            try {
                initializeInterpreter()
                task.setResult(null)
            } catch (e: IOException) {
                task.setException(e)
            }
        }
        task.task.addOnFailureListener { e -> Log.e(TAG, "Error to setting up digit classifier.", e) }
    }
    @Throws(IOException::class)
    private fun initializeInterpreter() {
        // Load the TF Lite model
        val assetManager = context.assets
        val model = loadModelFile(assetManager)
        // Initialize TF Lite Interpreter with NNAPI enabled
        val options = Interpreter.Options()
        // 测试发现 NNAPI 对 MaxPooling1D 有支持问题,如果遇到在手机端预测和python预测不准问题可以尝试关掉 NNAPI, 再check下
        options.setUseNNAPI(true)
        val interpreter = Interpreter(model, options)
        // Read input shape from model file
        val inputShape = interpreter.getInputTensor(0).shape()
        val simpleCount = inputShape[1]
        val tensorSize = inputShape[2]
        modelInputSize = FLOAT_TYPE_SIZE * simpleCount * tensorSize * PIXEL_SIZE
        val outputShape = interpreter.getOutputTensor(0).shape()
        // Finish interpreter initialization
        this.interpreter = interpreter
        isInitialized = true
        Log.d(TAG, "Initialized TFLite interpreter. inputShape:${Arrays.toString(inputShape)}, outputShape:${Arrays.toString(outputShape)}")
    }
    @Throws(IOException::class)
    private fun loadModelFile(assetManager: AssetManager): ByteBuffer {
        val fileDescriptor = assetManager.openFd(MODEL_FILE) // 使用全连接网络模型
        // val fileDescriptor = assetManager.openFd(MODEL_CNN_FILE) // 使用卷积神经网络模型
        val inputStream = FileInputStream(fileDescriptor.fileDescriptor)
        val fileChannel = inputStream.channel
        val startOffset = fileDescriptor.startOffset
        val declaredLength = fileDescriptor.declaredLength
        return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength)
    }
    private fun classify(pointList: JSONArray): ClassifierLabelResult {
        if (!isInitialized) {
            throw IllegalStateException("TF Lite Interpreter is not initialized yet.")
        }
        try {
            // Preprocessing: resize the input
            var startTime: Long = System.nanoTime()
            val byteBuffer = convertFloatArrayToByteBuffer(pointList)
            var elapsedTime = (System.nanoTime() - startTime) / 1000000
            Log.d(TAG, "Preprocessing time = " + elapsedTime + "ms")
            startTime = System.nanoTime()
            val result = Array(1) { FloatArray(OUTPUT_CLASSES_COUNT) }
            interpreter?.run(byteBuffer, result)
            elapsedTime = (System.nanoTime() - startTime) / 1000000
            Log.d(TAG, "Inference time = " + elapsedTime + "ms result=" + result[0].contentToString())
            // return top 4
            val output = result[0][0]
            return if (output > 0.5f) {
                ClassifierLabelResult(output, "right", labelRight)
            } else {
                ClassifierLabelResult(1.0f-output, "left", labelLeft)
            }
        } catch (e: Throwable) {
            Log.e(TAG, "Inference error", e)
        }
        return ClassifierLabelResult(-1f, "unknown", labelUnknown)
    }
    fun classifyAsync(pointList: JSONArray): Task<ClassifierLabelResult> {
        val task = TaskCompletionSource<ClassifierLabelResult>()
        executorService.execute {
            val result = classify(pointList)
            task.setResult(result)
        }
        return task.task
    }
    fun close() {
        executorService.execute {
            interpreter?.close()
            Log.d(TAG, "Closed TFLite interpreter.")
        }
    }
    private fun convertFloatArrayToByteBuffer(pointList: JSONArray): ByteBuffer {
        Log.d(TAG, "convertFloatArrayToByteBuffer pointList=$pointList")
        val byteBuffer = ByteBuffer.allocateDirect(modelInputSize)
        byteBuffer.order(ByteOrder.nativeOrder())
        val step = pointList.length().toFloat() / sampleCount
        for (i in 0 until sampleCount) {
            val e = pointList[(i * step).toInt()] as JSONArray
            for (j in 0 until tensorSize) {
                val value = (e[j] as Number).toFloat() // x y w h density dtime
                byteBuffer.putFloat(value)
            }
        }
        return byteBuffer
    }
    companion object {
        private const val TAG = "ClientAI#Classifier"
        private const val MODEL_FILE = "mymodel.tflite"
        private const val FLOAT_TYPE_SIZE = 4
        private const val PIXEL_SIZE = 1
        private const val OUTPUT_CLASSES_COUNT = 1
        const val sampleCount = 9
        const val tensorSize = 6
        const val labelLeft = 0;
        const val labelRight = 1;
        const val labelUnknown = -1;
    }
}
class ClassifierLabelResult(var score: Float, var label: String ,val labelInt: Int) {
    override fun toString(): String {
        val format = DecimalFormat("#.##")
        return "$label score:${format.format(score)}"
    }
}
复制代码
class MotionEventTracker(var context: Context) {
    companion object {
        const val TAG = "ClientAI#tracker"
    }
    interface ITrackDataReadyListener {
        fun onTrackDataReady(dataList: JSONArray)
    }
    private var width = 0
    private var height = 0
    private var density = 1f
    private var listener: ITrackDataReadyListener? = null
    fun checkAndInit(listener: ITrackDataReadyListener) {
        this.listener = listener
        val metric = context.resources.displayMetrics
        width = min(metric.widthPixels, metric.heightPixels)
        height = max(metric.widthPixels, metric.heightPixels)
        density = metric.density
    }
    private var currentEvents: JSONArray? = null
    private var currentDownTime = 0L
    fun recordMotionEvent(ev: MotionEvent) {
        if (ev.pointerCount > 1) {
            currentEvents = null
            return
        }
        if (ev.action == MotionEvent.ACTION_DOWN) {
            currentEvents = JSONArray()
            currentDownTime = ev.eventTime
        }
        if (currentEvents != null) {
            if (ev.historySize > 0) {
                for (i in 0 until ev.historySize) {
                    currentEvents?.put(buildPoint(ev.getHistoricalX(i), ev.getHistoricalY(i), ev.getHistoricalEventTime(i)))
                }
            }
            currentEvents?.put(buildPoint(ev.x, ev.y, ev.eventTime))
        }
        if (ev.action == MotionEvent.ACTION_UP) {
            currentEvents?.let {
                if (it.length() >= 6) {
                    listener?.onTrackDataReady(it) // 触发预测
                    Log.i(TAG, "cache events, eventCount=${it.length()}, data=$it")
                } else {
                    // 过滤点击和误触轨迹
                    Log.i(TAG, "skipped short events, eventCount=${it.length()}, data=$it")
                }
            }
            currentEvents = null
        }
    }
    private fun buildPoint(x: Float, y: Float, timestamp: Long): JSONArray {
        val point = JSONArray()
        point.put(x)
        point.put(y)
        point.put(width)
        point.put(height)
        point.put(density)
        point.put(timestamp - currentDownTime)
        return point
    }
}
复制代码


相关文章
|
2月前
|
Web App开发 Java 视频直播
FFmpeg开发笔记(四十九)助您在毕业设计中脱颖而出的几个流行APP
对于软件、计算机等专业的毕业生,毕业设计需实现实用软件或APP。新颖的设计应结合最新技术,如5G时代的音视频技术。示例包括: 1. **短视频分享APP**: 集成FFmpeg实现视频剪辑功能,如添加字幕、转场特效等。 2. **电商购物APP**: 具备直播带货功能,使用RTMP/SRT协议支持流畅直播体验。 3. **同城生活APP**: 引入WebRTC技术实现可信的视频通话功能。这些应用不仅实用,还能展示开发者紧跟技术潮流的能力。
85 4
FFmpeg开发笔记(四十九)助您在毕业设计中脱颖而出的几个流行APP
|
3月前
|
Web App开发 Android开发
FFmpeg开发笔记(四十六)利用SRT协议构建手机APP的直播Demo
实时数据传输在互联网中至关重要,不仅支持即时通讯如QQ、微信的文字与图片传输,还包括音视频通信。一对一通信常采用WebRTC技术,如《Android Studio开发实战》中的App集成示例;而一对多的在线直播则需部署独立的流媒体服务器,使用如SRT等协议。SRT因其优越的直播质量正逐渐成为主流。本文档概述了SRT协议的使用,包括通过OBS Studio和SRT Streamer进行SRT直播推流的方法,并展示了推流与拉流的成功实例。更多细节参见《FFmpeg开发实战》一书。
63 1
FFmpeg开发笔记(四十六)利用SRT协议构建手机APP的直播Demo
|
3月前
|
Web App开发 5G Linux
FFmpeg开发笔记(四十四)毕业设计可做的几个拉满颜值的音视频APP
一年一度的毕业季来临,计算机专业的毕业设计尤为重要,不仅关乎学业评价还积累实战经验。选择紧跟5G技术趋势的音视频APP作为课题极具吸引力。这里推荐三类应用:一是融合WebRTC技术实现视频通话的即时通信APP;二是具备在线直播功能的短视频分享平台,涉及RTMP/SRT等直播技术;三是具有自定义动画特效及卡拉OK歌词字幕功能的视频剪辑工具。这些项目不仅技术含量高,也符合市场需求,是毕业设计的理想选择。
80 6
FFmpeg开发笔记(四十四)毕业设计可做的几个拉满颜值的音视频APP
|
3月前
|
编解码 Java Android开发
FFmpeg开发笔记(四十五)使用SRT Streamer开启APP直播推流
​SRT Streamer是一个安卓手机端的开源SRT协议直播推流框架,可用于RTMP直播和SRT直播。SRT Streamer支持的视频编码包括H264、H265等等,支持的音频编码包括AAC、OPUS等等,可谓功能强大的APP直播框架。另一款APP直播框架RTMP Streamer支持RTMP直播和RTSP直播,不支持SRT协议的直播。而本文讲述的SRT Streamer支持RTMP直播和SRT直播,不支持RTSP协议的直播。有关RTMP Streamer的说明参见之前的文章《使用RTMP Streamer开启APP直播推流》,下面介绍如何使用SRT Streamer开启手机直播。
76 4
FFmpeg开发笔记(四十五)使用SRT Streamer开启APP直播推流
|
4月前
|
Web App开发 缓存 编解码
FFmpeg开发笔记(三十八)APP如何访问SRS推流的RTMP直播地址
《FFmpeg开发实战》书中介绍了轻量级流媒体服务器MediaMTX,适合测试RTSP/RTMP协议,但不适用于复杂直播场景。SRS是一款强大的开源流媒体服务器,支持多种协议,起初为RTMP,现扩展至HLS、SRT等。在FFmpeg 6.1之前,推送给SRS的HEVC流不受支持。要播放RTMP流,Android应用可使用ExoPlayer,需在`build.gradle`导入ExoPlayer及RTMP扩展,并根据URL类型创建MediaSource。若SRS播放黑屏,需在配置文件中开启`gop_cache`以缓存关键帧。
149 2
FFmpeg开发笔记(三十八)APP如何访问SRS推流的RTMP直播地址
|
3月前
|
测试技术
一款功能完善的智能匹配1V1视频聊天App应该通过的测试CASE
文章列举了一系列针对1V1视频聊天App的测试用例,包括UI样式、权限请求、登录流程、匹配逻辑、消息处理、充值功能等多个方面的测试点,并标注了每个测试用例的执行状态,如通过(PASS)、失败(FAIL)或需要进一步处理(延期修改、待定、方案再定等)。
66 0
|
5月前
|
编解码 Java Android开发
FFmpeg开发笔记(三十一)使用RTMP Streamer开启APP直播推流
RTMP Streamer是一款开源的安卓直播推流框架,支持RTMP、RTSP和SRT协议,适用于各种直播场景。它支持H264、H265、AV1视频编码和AAC、G711、OPUS音频编码。本文档介绍了如何使用Java版的RTMP Streamer,建议使用小海豚版本的Android Studio (Dolphin)。加载项目时,可添加国内仓库加速依赖下载。RTMP Streamer包含五个模块:app、encoder、rtmp、rtplibrary和rtsp。完成加载后,可以在手机上安装并运行APP,提供多种直播方式。开发者可以从《FFmpeg开发实战:从零基础到短视频上线》获取更多信息。
123 7
FFmpeg开发笔记(三十一)使用RTMP Streamer开启APP直播推流
|
5月前
|
移动开发 小程序 视频直播
FFmpeg开发笔记(二十七)解决APP无法访问ZLMediaKit的直播链接问题
本文讲述了在使用ZLMediaKit进行视频直播时,遇到移动端通过ExoPlayer和微信小程序播放HLS直播地址失败的问题。错误源于ZLMediaKit对HTTP地址的Cookie校验导致401无权限响应。通过修改ZLMediaKit源码,注释掉相关鉴权代码并重新编译安装,解决了此问题,使得ExoPlayer和小程序能成功播放HLS视频。详细解决方案及FFmpeg集成可参考《FFmpeg开发实战:从零基础到短视频上线》一书。
222 3
FFmpeg开发笔记(二十七)解决APP无法访问ZLMediaKit的直播链接问题
|
6月前
|
人工智能 UED iOS开发
最佳平替APP:智能消费新选择
【2月更文挑战第29天】最佳平替是一款AI应用,响应消费降级趋势,通过智能匹配帮用户找到价低质优的商品替代品,节省开支。用户输入商品名,AI推荐相似平替选项,提高购物效率。涵盖商品、旅游景点、学校等多个领域,提供跨界平替建议。尽管AI推荐有时不准确,开发团队正持续优化,旨在帮助用户理性消费,避免不必要的开支,已获得用户支持。
160 1
最佳平替APP:智能消费新选择
|
6月前
|
传感器 内存技术
毕业设计 江科大STM32的智能温室控制蓝牙声光报警APP系统设计
毕业设计 江科大STM32的智能温室控制蓝牙声光报警APP系统设计
186 0