使用Java 实现一个“你画手机猜”的小游戏

本文适合有 Java 基础的人群

作者:DJL-Lanking

HelloGitHub 推出的《讲解开源项目》系列。有幸邀请到了亚马逊 + Apache 的工程师:Lanking( https://github.com/lanking520 ),为我们讲解 DJL —— 完全由 Java 构建的深度学习平台,本文为系列的第三篇。

一、前言

在 2018 年时,Google 推出了《猜画小歌》应用:玩家可以直接与AI进行你画我猜的游戏。通过画出一个房子或者一个猫,AI 会推断出各种物品被画出的概率。它的实现得益于深度学习模型在其中的应用,通过深度神经网络的归纳,曾经令人头疼的绘画识别也变得易如反掌。现如今,只要使用一个简单的图片分类模型,我们便可以轻松的实现绘画识别。试试看这个在线涂鸦小游戏吧:

在线涂鸦小游戏:https://djl.ai/website/demo.html

在当时,大部分机器学习计算任务仍旧需要依托网络在云端进行。随着算力的不断增进,机器学习任务已经可以直接在边缘设备部署,包括各类运行安卓系统的智能手机。但是,由于安卓本身主要是用 Java ,部署基于 Python 的各类深度学习模型变成了一个难题。为了解决这个问题,AWS 开发并开源了 DeepJavaLibrary (DJL),一个为 Java 量身定制的深度学习框架。

在这个文章中,我们将尝试通过 PyTorch 预训练模型在在安卓平台构建一个涂鸦绘画的应用。由于总代码量会比较多,我们这次会挑重点把最关键的代码完成。你可以后续参考我们完整的项目进行构建。

涂鸦应用完整代码:https://github.com/aws-samples/djl-demo/tree/master/android

二、环境配置

为了兼容 DJL 需求的 Java 功能,这个项目需要 Android API 26 及以上的版本。你可以参考我们案例配置来节约一些时间,下面是这个项目需要的依赖项:

案例 gradle: https://github.com/aws-samples/djl-demo/blob/master/android/quickdraw_recognition/build.gradle

dependencies {
 implementation 'androidx.appcompat:appcompat:1.2.0'
 implementation 'ai.djl:api:0.7.0'
 implementation 'ai.djl.android:core:0.7.0'
 runtimeOnly 'ai.djl.pytorch:pytorch-engine:0.7.0'
 runtimeOnly 'ai.djl.android:pytorch-native:0.7.0'
}

我们将使用 DJL 提供的 API 以及 PyTorch 包。

三、构建应用

3.1 第一步:创建 Layout

我们可以先创建一个 View class 以及 layout(如下图)来构建安卓的前端显示界面。

如上图所示,你可以在主界面创建两个 View 目标。PaintView 是用来让用户画画的,在右下角 ImageView 是用来展示用于深度学习推理的图像。同时我们预留一个按钮来进行画板的清空操作。

3.2 第二步: 应对绘画动作

在安卓设备上,你可以自定义安卓的触摸事件响应来应对用户的各种触控操作。在我们的情况下,我们需要定义下面三种时间响应:

  • touchStart:感应触碰时触发
  • touchMove:当用户在屏幕上移动手指时触发
  • touchUp:当用户抬起手指时触发

与此同时,我们用 paths 来存储用户在画板所绘制的路径。现在我们看一下实现代码。

3.2.1 重写 OnTouchEventOnDraw 方法

现在我们重写 onTouchEvent 来应对各种响应:

@Override
public boolean onTouchEvent(MotionEvent event) {
 float x = event.getX();
 float y = event.getY();

 switch (event.getAction()) {
 case MotionEvent.ACTION_DOWN :
  touchStart(x, y);
  invalidate();
  break;
 case MotionEvent.ACTION_MOVE :
  touchMove(x, y);
  invalidate();
  break;
 case MotionEvent.ACTION_UP :
  touchUp();
  runInference();
  invalidate();
  break;
 }

 return true;
}

如上面代码所示,你可以添加一个 runInference 方法在 MotionEvent.ACTION_UP 事件响应上。这个方法是用来在用户绘制完后对结果进行推理。在之后的几步中,我们会讲解它的具体实现。

我们同样需要重写 onDraw 方法来展示用户绘制的图像:

@Override
protected void onDraw(Canvas canvas) {
 canvas.save();
 this.canvas.drawColor(DEFAULT_BG_COLOR);

 for (Path path : paths) {
 paint.setColor(DEFAULT_PAINT_COLOR);
 paint.setStrokeWidth(BRUSH_SIZE);
 this.canvas.drawPath(path, paint);
 }
 canvas.drawBitmap(bitmap, 0, 0, bitmapPaint);
 canvas.restore();
}

真正的图像会保存在一个 Bitmap 上。

3.2.2 操作开始(touchStart)

当用户触碰行为开始时,下面的代码会建立一个新的路径同时记录路径中每一个点在屏幕上的坐标。

private void touchStart(float x, float y) {
 path = new Path();
 paths.add(path);
 path.reset();
 path.moveTo(x, y);
 this.x = x;
 this.y = y;
}

3.2.3 手指移动(touchMove)

在手指移动中,我们会持续记录坐标点然后将它们构成一个 quadratic bezier. 通过一定的误差阀值来动态优化用户的绘画动作。只有差别超出误差范围内的动作才会被记录下来。

quadratic bezier 文档: https://developer.android.com/reference/android/graphics/Path

private void touchMove(float x, float y) {
 if (x < 0 || x > getWidth() || y < 0 || y > getHeight()) {
 return;
 }
 float dx = Math.abs(x - this.x);
 float dy = Math.abs(y - this.y);

 if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
 path.quadTo(this.x, this.y, (x + this.x) / 2, (y + this.y) / 2);
 this.x = x;
 this.y = y;
 }
}

3.2.4 操作结束(touchUp)

当触控操作结束后,下面的代码会绘制一个路径同时计算最小长方形目标框。

private void touchUp() {
 path.lineTo(this.x, this.y);
 maxBound.add(new Path(path));
}

3.3 第三步:开始推理

为了在安卓设备上进行推理任务,我们需要完成下面几个任务:

  • 从 URL 读取模型
  • 构建前处理和后处理过程
  • 从 PaintView 进行推理任务

为了完成以下目标,我们尝试构建一个 DoodleModel class。在这一步,我们将介绍一些完成这些任务的关键步骤。

3.3.1 读取模型

DJL 内建了一套模型管理系统。开发者可以自定义储存模型的文件夹。

File dir = getFilesDir();
System.setProperty("DJL_CACHE_DIR", dir.getAbsolutePath());

通过更改 DJL_CACHE_DIR 属性,模型会被存入相应路径下。

下一步可以通过定义 Criteria 从指定 URL 处下载模型。下载的 zip 文件内包含:

  • doodle_mobilenet.pt:PyTorch 模型
  • synset.txt:包含分类任务中所有类别的名称
Criteria<Image, Classifications> criteria =
  Criteria.builder()
   .setTypes(Image.class, Classifications.class)
   .optModelUrls("https://djl-ai.s3.amazonaws.com/resources/demo/pytorch/doodle_mobilenet.zip")
   .optTranslator(translator)
   .build();
return ModelZoo.loadModel(criteria);

上述代码同时定义了 translator,它会被用来做图片的前处理和后处理。

最后,如下述代码创建一个 Model 并用它创建一个 Predictor

@Override
protected Boolean doInBackground(Void... params) {
 try {
 model = DoodleModel.loadModel();
 predictor = model.newPredictor();
 return true;
 } catch (IOException | ModelException e) {
 Log.e("DoodleDraw", null, e);
 }
 return false;
}

更多关于模型加载的信息,请参阅如何加载模型。

DJL 模型加载文档:http://docs.djl.ai/docs/load_model.html

3.3.2 用 Translator 定义前处理和后处理

在 DJL 中,我们定义了 Translator 接口进行前处理和后处理。在 DoodleModel 中我们定义了 ImageClassificationTranslator 来实现 Translator:

ImageClassificationTranslator.builder()
 .addTransform(new ToTensor())
 .optFlag(Image.Flag.GRAYSCALE)
 .optApplySoftmax(true).build());

下面我们详细阐述 translator 所定义的前处理和后处理如何被用在模型的推理步骤中。当你创建 translator 时,内部程序会自动加载 synset.txt 文件得到做分类任务时所有类别的名称。当模型的 predict() 方法被调用时,内部程序会先执行所对应的 translator 的前处理步骤,而后执行实际推理步骤,最后执行 translator 的后处理步骤。对于前处理,我们会将 Image 转化 NDArray,用于作为模型推理过程的输入。对于后处理,我们对推理输出的结果(NDArray)进行 softmax 操作。最终返回结果为 Classifications 的一个实例。

自定义 Translator 案例:http://docs.djl.ai/jupyter/pytorch/load_your_own_pytorch_bert.html

3.3.3 用 PaintView 进行推理任务

最后,我们来实现之前定义好的 runInference 方法。

public void runInference() {
 // 拷贝图像
 Bitmap bmp = Bitmap.createBitmap(bitmap);
 // 缩放图像
 bmp = Bitmap.createScaledBitmap(bmp, 64, 64, true);
 // 执行推理任务
 Classifications classifications = model.predict(bmp);
 // 展示输入的图像
 Bitmap present = Bitmap.createScaledBitmap(bmp, imageView.getWidth(), imageView.getHeight(), true);
 imageView.setImageBitmap(present);
 // 展示输出的图像
 if (messageToast != null) {
 messageToast.cancel();
 }
 messageToast = Toast.makeText(getContext(), classifications.toString(), Toast.LENGTH_SHORT);
 messageToast.show();
}

这将会创建一个 Toast 弹出页面用于展示结果,示例如下:

恭喜你!我们完成了一个涂鸦识别小程序!

3.4 可选优化:输入裁剪

为了得到更高的模型推理准确度,你可以通过截取图像来去除无意义的边框部分。

上面右侧的图片会比左边的图片有更好的推理结果,因为它所包含的空白边框更少。你可以通过 Bound 类来寻找图片的有效边界,即能把图中所有白色像素点覆盖的最小矩形。在得到 x 轴最左坐标,y 轴最上坐标,以及矩形高度和宽度后,就可以用这些信息截取出我们想要的图形(如右图所示)实现代码如下:

RectF bound = maxBound.getBound();
int x = (int) bound.left;
int y = (int) bound.top;
int width = (int) Math.ceil(bound.width());
int height = (int) Math.ceil(bound.height());
// 截取部分图像
Bitmap bmp = Bitmap.createBitmap(bitmap, x, y, width, height);

恭喜你!现在你就掌握了全部教程内容!期待看到你创建的第一个 DoodleDraw 安卓游戏!

最后,可以在GitHub找到本教程的完整案例代码。

涂鸦应用完整代码:https://github.com/aws-samples/djl-demo/tree/master/android

关于 DJL

Deep Java Library (DJL) 是一个基于 Java 的深度学习框架,同时支持训练以及推理。 DJL 博取众长,构建在多个深度学习框架之上 (TenserFlow、PyTorch、MXNet 等) 也同时具备多个框架的优良特性。你可以轻松使用 DJL 来进行训练然后部署你的模型。

它同时拥有着强大的模型库支持:只需一行便可以轻松读取各种预训练的模型。现在 DJL 的模型库同时支持高达 70 个来自 GluonCV、 HuggingFace、TorchHub 以及 Keras 的模型。

项目地址:https://github.com/awslabs/djl/

到此这篇关于使用Java 实现一个“你画手机猜”的小游戏的文章就介绍到这了,更多相关Java实现你画手机猜小游戏内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java桌球小游戏 小球任意角度碰撞

    本文实例为大家分享了java桌球小游戏的具体代码,供大家参考,具体内容如下 import javax.swing.*; import java.awt.*; public class BallGame extends JFrame { /** *继承swing里面的窗口类 */ //加载图片 Image ball = Toolkit.getDefaultToolkit().getImage("images/ball.jpg.png");/*得到系统默认的工具包*/ Image desk

  • java实现桌球游戏

    本文实例为大家分享了java实现桌球游戏的具体代码,供大家参考,具体内容如下 思维 1窗口加载即创建一个窗口  2在窗口内显示图片,先后顺序(桌面,球)否者会被覆盖掉.  3.更改球的x,y,刷新窗口 ecipse 1.建立于src同级的new folder new->folder---->image(文件名) 图片直接从外部cpoy,在文件名上复制 2.下面直接完整代码注释都在里边 package day01; import java.awt.*; //导包 import javax.swi

  • java实现一个桌球小游戏

    本文实例为大家分享了java实现桌球小游戏的具体代码,供大家参考,具体内容如下 在ecplise中新建一个JAVA项目 建立四个class分别对应游戏登陆界面,游戏界面,数据库操作,和一个开始类 代码如下 游戏类: package 弹球游戏; import java.awt.BorderLayout; import java.awt.Canvas; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; im

  • java猜数字小游戏案例

    本文实例为大家分享了java猜数字小游戏案例,供大家参考,具体内容如下 package day08; import java.util.Scanner; public class GuessNumber { /** * 猜数字小游戏:. 需求: 随机产生一个整数数1-100(被猜数)不设置输出 * 键盘录入一个玩家要猜的数字 * 判断猜大还是猜小了或者猜中 */ public static void main(String[] args) { // 随机产生整数1-100 int number

  • java弹幕小游戏1.0版本

    java 弹幕小游戏的最初版本,供大家参考,具体内容如下 最近在学习javaSE,根据b站视频老师的讲解,也参考了他的代码,做了一个弹幕小游戏,也增添了一些自己的代码进去,因为只是最简单的游戏体,以后会慢慢做完整,所以如果有错误,或者代码不够整洁的话,可以帮我改正,谢谢啦. 父类 import java.awt.*; public class GameObject { //游戏物体的父类 Image img; double x,y; int speed = 3; int width,height

  • Java实现猜字小游戏

    猜字游戏是一款益智游戏,喜欢玩具有挑战性单词游戏的你就可以来体验一下了,给你一个字母板.尝试通过想象相邻字母的单词.您将获得一个分数,根据您使用的字母,你已经使用了多少个字母,并以这些字母相关联的任何修饰符都可以的. 具体代码如下所示: package test07; import java.util.Scanner; //猜字符游戏 public class Guessing { //主方法 public static void main(String[] args) { Scanner sc

  • 使用Java 实现一个“你画手机猜”的小游戏

    本文适合有 Java 基础的人群 作者:DJL-Lanking HelloGitHub 推出的<讲解开源项目>系列.有幸邀请到了亚马逊 + Apache 的工程师:Lanking( https://github.com/lanking520 ),为我们讲解 DJL -- 完全由 Java 构建的深度学习平台,本文为系列的第三篇. 一.前言 在 2018 年时,Google 推出了<猜画小歌>应用:玩家可以直接与AI进行你画我猜的游戏.通过画出一个房子或者一个猫,AI 会推断出各种物

  • Java如何获取一个随机数 Java猜数字小游戏

    本文实例为大家分享了Java获取一个随机数(及猜数字小游戏)的具体代码,供大家参考,具体内容如下 Math类概述: 该类Math包含用于执行基本数值运算的方法,例如基本指数,对数,平方根和三角函数. Math所有类都是静态的.可以直接类名.调用. 获取随机数的办法: 代码如下 public class SuiJi { public static void main(String[] args) { double d = Math.random(); System.out.println(d);

  • linux实现猜数字小游戏源码

    一个简单的linux猜数字小游戏源码 游戏规则: 猜数字游戏通常由两个人玩,一方出数字,一方猜.出数字的人要想好一个没有重复数字的 4 个数,不能让猜的人知道.猜的人就可以开始猜.每猜一个数字,出数者就要根据这个数字给出几 A 几 B,其中 A 前面的数字表示位置正确的数的个数,而 B 前的数字表示数字正确而位置不对的数的个数.如正确答案为 5234,而猜的人猜 5346,则是 1A2B,其中有一个 5 的位置对了,记为 1A,而 3 和 4 这两个数字对了,而位置没对,因此记为 2B,合起来就

  • Java编写猜数字小游戏

    本文实例讲述了java实现的简单猜数字游戏代码.分享给大家供大家参考. 以下是Java语言写的一个猜数字小游戏引用片段: import java.text.SimpleDateFormat; import java.util.Date; import java.util.Scanner; //主函数 public class calssOne { public static void main(String[] args) { //shit+Ctrl+o int result; //随机产生一个

  • java学习之猜数字小游戏

    今天主要学习了一些初级的设计,完成了这个猜数字的小游戏,其是也算不上是什么游戏,因为我答案都给出来了.当然也是想对代码更加熟练的操作,让自己能够得心应手. 这个小程序中让我花了点时间的主要是那个如何去重的问题,当时也是思考良久,后来才考虑到使用死循环让随机数产生直到不重复为止,其他感觉也还好. import java.util.Scanner; public class GuessingGames { public static void main(String[] args) { Scanne

  • java实现猜数字小游戏(Swing版)

    2008年的时候,在学习Java how to program第五版的时候,写过一个猜数字小游戏,是用Applet写的: 现在,我要用Swing重写这个小游戏,同时,加入一些新功能,如:背景颜色(红色表示偏高,蓝色表示偏低).弹框.字体控制.布局管理器的使用. 运行截屏: 代码如下: //Guess a number between 1 and 1000 //Java how to program, 10/e, Exercise 12.14 //by pandenghuang@163.com /

  • Java Socket实现猜数字小游戏

    本文实例为大家分享了Java Socket实现猜数字游戏的具体代码,供大家参考,具体内容如下 运行截图 Server Client 完整代码 Server import java.io.IOException; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Random; import java.util.Scanner; public clas

  • Java实现猜数字小游戏详解流程

    猜数字游戏 系统自动生成一个随机整数(1-100), 然后由用户输入一个猜测的数字. 如果输入的数字比该随机数小, 提示 "低 了", 如果输入的数字比该随机数大, 提示 "高了" , 如果输入的数字和随机数相等, 则提示 "猜对了 整理思路 1. 我们玩游戏的时候,都有开始游戏和退出游戏 2. 其次,它要生成一个随机数,如果是固定值,哪有什么意思? 3. 再者,我们要输入数字,根据它反馈的情况进行判断和猜测数字的大小 4. 但是我们不可能说一次就判断成功

随机推荐