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
import mediapipe as mp
import cv2
import numpy as npmp_hands = mp.solutions.hands
hands = mp_hands.Hands()def detect_hand(image):image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)results = hands.process(image)if results.multi_hand_landmarks:return [(lm.x, lm.y) for lm in results.multi_hand_landmarks[0].landmark]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
from libc.stdlib cimport malloc, free
import mediapipe as mp
import cv2
import numpy as np
cimport numpy as npmp_hands = mp.solutions.hands
hands = mp_hands.Hands()def detect_hand(unsigned char[:] image_data, int width, int height):cdef np.ndarray[np.uint8_t, ndim=3] image = np.array(image_data).reshape((height, width, 3))image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)results = hands.process(image)if results.multi_hand_landmarks:return [(lm.x, lm.y) for lm in results.multi_hand_landmarks[0].landmark]return []
這里的 detect_hand
現在使用了 Cython 的類型注解,從而提高執行效率。
5.2 創建 setup.py
編譯 Cython 代碼
from setuptools import setup
from Cython.Build import cythonize
import numpysetup(ext_modules=cythonize("hand_tracking.pyx"),include_dirs=[numpy.get_include()]
)
運行以下命令進行編譯:
python setup.py build_ext --inplace
成功后會生成一個 .so
或 .pyd
文件,這是我們的 C 語言擴展。
6. 使用 JNI 調用 Cython 生成的 C 代碼
6.1 創建 JNI C 代碼
在 jni/
目錄下創建 hand_tracking_jni.c
:
#include <jni.h>
#include <stdio.h>JNIEXPORT jstring JNICALL
Java_com_example_myapp_HandTracking_detectHand(JNIEnv *env, jobject thiz, jbyteArray imageData, jint width, jint height) {// 這里調用 Cython 生成的 C 代碼return (*env)->NewStringUTF(env, "手勢檢測結果");
}
6.2 配置 Android.mk
和 Application.mk
Android.mk
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)
LOCAL_MODULE := hand_tracking
LOCAL_SRC_FILES := hand_tracking_jni.c
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
Application.mk
APP_ABI := armeabi-v7a arm64-v8a
APP_PLATFORM := android-21
然后使用 NDK 編譯:
ndk-build
7. 在 Android App 中調用本地庫
在 MainActivity.java
中調用 JNI 代碼:
package com.example.myapp;public class HandTracking {static {System.loadLibrary("hand_tracking");}public native String detectHand(byte[] imageData, int width, int height);
}
在 MainActivity.java
里調用:
HandTracking handTracking = new HandTracking();
String result = handTracking.detectHand(imageData, width, height);
Log.d("HandTracking", "檢測結果: " + result);
8. 運行和調試
-
構建 Cython 代碼:
python setup.py build_ext --inplace
-
使用 NDK 編譯 JNI 代碼:
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 模塊 到動態庫
希望本教程對你有所幫助!🚀