page contents

java游戏制作教程——用 Java 做个“你画手机猜”的安卓小游戏

本文讲述了Java游戏制作教程——用 Java 做个“你画手机猜”的安卓小游戏!具有很好的参考价值,希望对大家有所帮助。一起跟随六星小编过来看看吧,具体如下:

attachments-2023-06-XgxIvmZr6483d03f0c3f8.png本文讲述了Java游戏制作教程——用 Java 做个“你画手机猜”的安卓小游戏!具有很好的参考价值,希望对大家有所帮助。一起跟随六星小编过来看看吧,具体如下:

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

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

在当时,大部分机器学习计算任务仍旧需要依托网络在云端进行。随着算力的不断增进,机器学习任务已经可以直接在边缘设备部署,包括各类运行安卓系统的智能手机。但是,由于安卓本身主要是用 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(如下图)来构建安卓的前端显示界面。

attachments-2023-06-Jp5kMYBB6483cff3a58e8.png

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


3.2 第二步: 应对绘画动作

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


touchStart:感应触碰时触发

touchMove:当用户在屏幕上移动手指时触发

touchUp:当用户抬起手指时触发

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


3.2.1 重写 OnTouchEvent 和 OnDraw 方法

现在我们重写 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 弹出页面用于展示结果,示例如下:

attachments-2023-06-afwFLzJs6483cfcf2051c.png

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

3.4 可选优化:输入裁剪

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

attachments-2023-06-k7KXkWEM6483cfb7b38bf.png

上面右侧的图片会比左边的图片有更好的推理结果,因为它所包含的空白边框更少。你可以通过 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 安卓游戏!

更多相关技术内容咨询欢迎前往并持续关注六星社区了解详情。

想高效系统的学习Java编程语言,推荐大家关注一个微信公众号:Java圈子。每天分享行业资讯、技术干货供大家阅读,关注即可免费领取整套Java入门到进阶的学习资料以及教程,感兴趣的小伙伴赶紧行动起来吧。

attachments-2023-03-2AoKIjPQ64014b4ad30a3.jpg

  • 发表于 2023-06-10 09:22
  • 阅读 ( 124 )
  • 分类:Java开发

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
轩辕小不懂
轩辕小不懂

2403 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 1316 文章
  3. Pack 1135 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章