从零开始:利用 Cython + JNI 在 Android 上运行 Python 算法

打印 上一主题 下一主题

主题 1849|帖子 1849|积分 5547

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
1. 引言

在 Android 设备上运行 Python 代码通常面临性能、兼容性和封装等挑战。尤其是当你希望在 Android 应用中利用 Python 编写的计算密集型算法时,直接运行 Python 代码大概导致较高的 CPU 占用和较差的性能。为了解决这个题目,我们可以利用 Cython 将 Python 代码编译成 C 扩展,并通过 JNI(Java Native Interface) 在 Android 上调用这些 C 代码,从而实现高效的 Python 代码实行。
本教程将先容如何在 Android 设备上利用 Cython 和 JNI,将 Python 代码转换为 Android 可用的本地库,并通过 Java 代码调用它。我们将以一个简朴的 手势识别 算法为例,展示完整的实现过程。

2. 项目概述

本教程的目标是:

  • 利用 Cython 将 Python 代码转换为 C 语言扩展,提高实行效率。
  • 利用 JNI 将 C 语言扩展封装成 Android 可调用的库。
  • 在 Android App 中集成 并调用这个 Python 代码转换的本地库。
我们假设你的 Python 代码是一个 基于 MediaPipe 的手部关键点检测算法,并希望它在 Android 设备上运行并返回检测效果。

3. 环境预备

在开始之前,确保你已经安装了以下工具和软件:


  • Android Studio(用于 Android 开发
  • NDK(Native Development Kit)(用于编译 JNI 代码)
  • Python 3.x
  • Cython
  • Linux 或 Windows 环境
  • 一个 Python 代码库(包含手势识别算法)
如果你利用的是 Windows,发起安装 WSL(Windows Subsystem for Linux) 大概利用 MSYS2 进行交叉编译。

4. 预备 Python 代码

首先,我们假设你已经有一个 Python 代码 hand_tracking.py,该代码利用 MediaPipe 识别手部关键点,并返回关键点坐标。
hand_tracking.py

  1. import mediapipe as mp
  2. import cv2
  3. import numpy as np
  4. mp_hands = mp.solutions.hands
  5. hands = mp_hands.Hands()
  6. def detect_hand(image):
  7.     image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
  8.     results = hands.process(image)
  9.     if results.multi_hand_landmarks:
  10.         return [(lm.x, lm.y) for lm in results.multi_hand_landmarks[0].landmark]
  11.     return []
复制代码
该函数接收 OpenCV 读取的 image,然后利用 MediaPipe 进行手部关键点检测,并返回关键点的 (x, y) 坐标。

5. 利用 Cython 将 Python 代码转换为 C 语言

5.1 编写 Cython 代码

Cython 答应我们将 Python 代码编译为 C 语言模块,从而提高实行效率。我们须要创建 hand_tracking.pyx 并将 hand_tracking.py 代码迁移到 Cython 代码中。
hand_tracking.pyx

  1. from libc.stdlib cimport malloc, free
  2. import mediapipe as mp
  3. import cv2
  4. import numpy as np
  5. cimport numpy as np
  6. mp_hands = mp.solutions.hands
  7. hands = mp_hands.Hands()
  8. def detect_hand(unsigned char[:] image_data, int width, int height):
  9.     cdef np.ndarray[np.uint8_t, ndim=3] image = np.array(image_data).reshape((height, width, 3))
  10.     image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
  11.     results = hands.process(image)
  12.     if results.multi_hand_landmarks:
  13.         return [(lm.x, lm.y) for lm in results.multi_hand_landmarks[0].landmark]
  14.     return []
复制代码
这里的 detect_hand 现在利用了 Cython 的范例注解,从而提高实行效率。
5.2 创建 setup.py 编译 Cython 代码

  1. from setuptools import setup
  2. from Cython.Build import cythonize
  3. import numpy
  4. setup(
  5.     ext_modules=cythonize("hand_tracking.pyx"),
  6.     include_dirs=[numpy.get_include()]
  7. )
复制代码
运行以下命令进行编译:
  1. python setup.py build_ext --inplace
复制代码
乐成后会天生一个 .so 或 .pyd 文件,这是我们的 C 语言扩展。

6. 利用 JNI 调用 Cython 天生的 C 代码

6.1 创建 JNI C 代码

在 jni/ 目次下创建 hand_tracking_jni.c:
  1. #include <jni.h>
  2. #include <stdio.h>
  3. JNIEXPORT jstring JNICALL
  4. Java_com_example_myapp_HandTracking_detectHand(JNIEnv *env, jobject thiz, jbyteArray imageData, jint width, jint height) {
  5.     // 这里调用 Cython 生成的 C 代码
  6.     return (*env)->NewStringUTF(env, "手势检测结果");
  7. }
复制代码
6.2 配置 Android.mk 和 Application.mk

Android.mk

  1. LOCAL_PATH := $(call my-dir)
  2. include $(CLEAR_VARS)
  3. LOCAL_MODULE := hand_tracking
  4. LOCAL_SRC_FILES := hand_tracking_jni.c
  5. LOCAL_LDLIBS := -llog
  6. include $(BUILD_SHARED_LIBRARY)
复制代码
Application.mk

  1. APP_ABI := armeabi-v7a arm64-v8a
  2. APP_PLATFORM := android-21
复制代码
然后利用 NDK 编译
  1. ndk-build
复制代码

7. 在 Android App 中调用本地库

在 MainActivity.java 中调用 JNI 代码:
  1. package com.example.myapp;
  2. public class HandTracking {
  3.     static {
  4.         System.loadLibrary("hand_tracking");
  5.     }
  6.     public native String detectHand(byte[] imageData, int width, int height);
  7. }
复制代码
在 MainActivity.java 里调用:
  1. HandTracking handTracking = new HandTracking();
  2. String result = handTracking.detectHand(imageData, width, height);
  3. Log.d("HandTracking", "检测结果: " + result);
复制代码

8. 运行和调试


  • 构建 Cython 代码
    1. python setup.py build_ext --inplace
    复制代码
  • 利用 NDK 编译 JNI 代码
    1. ndk-build
    复制代码
  • 运行 Android Studio 并在真机上测试
如果运行乐成,你应该能看到 Java 代码乐成调用了 detect_hand,并返回了手势检测的效果。

9. 结论

本教程展示了如何 利用 Cython + JNI 在 Android 上运行 Python 代码,实现高效的 Python 计算逻辑封装到 Android 应用中。你可以利用相同的方法,将 呆板学习、图像处置惩罚、语音识别等 Python 代码迁移到 Android,大大提升 Android 设备上的 AI 处置惩罚能力。
如果你想进一步优化,可以考虑:


  • 利用 TensorFlow Lite 替换 MediaPipe
  • 利用 OpenCL / Vulkan 进行 GPU 加快
  • 封装多个 Python 模块 到动态库
希望本教程对你有所帮助!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

没腿的鸟

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表