- 使用 Kivy + Pyjnius: 在一个完整的 Python GUI 框架(Kivy)中,通过一个桥梁(Pyjnius)调用 Android 的 Java/Kotlin 原生代码来访问摄像头。
- 使用 Chaquopy: 这是一个将 Python 集成到 Android Gradle 项目中的插件,你可以把它看作是在一个标准的 Android App 中嵌入了一个 Python 解释器,从而可以在 Java/Kotlin 代码中调用 Python,反之亦然。
这两种方式各有优劣,我会为你详细介绍第一种(Kivy + Pyjnius),因为它更贴近纯 Python 开发者的思维,且社区资源更丰富,然后我会简要介绍第二种方式。
使用 Kivy + Pyjnius (推荐)
这种方法的核心思想是:
- Kivy: 负责创建用户界面(UI),比如一个按钮来触发拍照,一个用来显示预览或拍摄到的图片。
- Pyjnius: 负责与 Android 系统交互,它允许你调用 Android 的 Java API,特别是
android.hardware.Camera来控制摄像头。
准备工作
- 安装 Python 环境: 你需要一个能在 Android 上运行 Python 的环境,最常用的是 Termux,在手机上从 F-Droid 或 Google Play 安装 Termux。
- 安装 Kivy 和 Pyjnius: 在 Termux 中运行以下命令:
pkg update && pkg upgrade pkg install python pip install kivy pip install pyjnius
- 准备 Android 设备: 确保你的 Android 设备已开启“开发者选项”和“USB 调试”。
项目结构
一个简单的项目结构如下:
python_android_camera/
├── main.py # 你的 Python 主程序
├── buildozer.spec # Buildozer 配置文件 (用于打包 APK)
└── (其他资源文件)
第 1 步:编写 Python 代码 (main.py)
这个文件将包含所有逻辑,我们会创建一个简单的界面,一个按钮用于拍照,一个 Image 控件用于显示结果。
# main.py
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.image import Image
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from jnius import autoclass, cast
# 1. 导入 Android Java 类
# Android 的上下文
PythonActivity = autoclass('org.kivy.android.PythonActivity')
# Android 的 SurfaceView,用于显示摄像头预览
SurfaceView = autoclass('android.view.SurfaceView')
# Android 的 SurfaceHolder
SurfaceHolder = autoclass('android.view.SurfaceHolder')
# Android 的 SurfaceHolder.Callback
SurfaceHolder_Callback = autoclass('android.view.SurfaceHolder$Callback')
# Android 的相机类
Camera = autoclass('android.hardware.Camera')
# Android 的相机参数类
Camera_Parameters = autoclass('android.hardware.Camera$Parameters')
# 用于存储图片的字节流
ByteArrayOutputStream = autoclass('java.io.ByteArrayOutputStream')
# 2. 定义一个 Java 回调类,用于处理 Surface 的事件
# 这个类实现了 SurfaceHolder.Callback 接口
class SurfaceHolderCallback(SurfaceHolder_Callback):
def __init__(self, camera_instance, preview_image_widget):
super(SurfaceHolderCallback, self).__init__()
self.camera = camera_instance
self.preview_image = preview_image_widget
def surfaceCreated(self, holder):
# 当 Surface 创建时,开始相机预览
print("Surface Created")
try:
self.camera.setPreviewDisplay(holder)
self.camera.startPreview()
except Exception as e:
print(f"Error starting preview: {e}")
def surfaceChanged(self, holder, format, width, height):
# 当 Surface 改变时,更新相机预览
print("Surface Changed")
self.camera.stopPreview()
parameters = self.camera.getParameters()
# 设置预览尺寸 (这里需要根据设备调整)
parameters.setPreviewSize(640, 480)
self.camera.setParameters(parameters)
self.camera.startPreview()
def surfaceDestroyed(self, holder):
# 当 Surface 销毁时,停止相机预览并释放相机
print("Surface Destroyed")
self.camera.stopPreview()
self.camera.release()
self.camera = None
# 3. 定义 Kivy App
class CameraApp(App):
def build(self):
# 主布局
self.layout = BoxLayout(orientation='vertical')
# 用于显示预览的 Image 控件
# 注意:Kivy 的 Image 控件本身不能直接显示相机流,我们这里简化处理,
# 或者使用第三方库如 kivy-garden.camera 来实现真正的预览。
# 对于这个例子,我们专注于拍照功能。
self.preview = Image(source='', size_hint=(1, 0.8))
self.layout.add_widget(self.preview)
# 拍照按钮
self.btn_capture = Button(text='Capture Photo', size_hint=(1, 0.2))
self.btn_capture.bind(on_press=self.capture_photo)
self.layout.add_widget(self.btn_capture)
# 获取 Android 的主布局
self.root = self.layout
# 初始化相机
self.init_camera()
return self.root
def init_camera(self):
# 获取相机实例 (0 通常是后置摄像头)
self.camera = Camera.open(0)
if self.camera is None:
print("Could not open camera!")
return
# 设置相机参数
parameters = self.camera.getParameters()
# 设置拍照图片尺寸
parameters.setPictureSize(1280, 960)
# 设置闪光灯模式 (可选)
# parameters.setFlashMode(Camera_Parameters.FLASH_MODE_AUTO)
self.camera.setParameters(parameters)
# 创建 SurfaceHolder 回调
# 注意:在 Kivy 中直接使用 SurfaceView 会更复杂,这里为了简化示例,
# 我们跳过了预览部分,直接专注于拍照。
# 完整的预览实现需要将 Kivy 的 Window 与 Android 的 SurfaceView 绑定。
# 这里我们假设相机已经初始化。
def capture_photo(self, instance):
if not self.camera:
print("Camera is not initialized!")
return
print("Capturing photo...")
# 创建一个字节数组输出流来接收图片数据
picture_stream = ByteArrayOutputStream()
try:
# 调用相机的 takePicture 方法
# 这个方法需要一个 ShutterCallback, PictureCallback, PictureCallback
# 我们只关心 JPEG 数据,所以只实现 PictureCallback
self.camera.takePicture(None, None,
autoclass('android.hardware.Camera$PictureCallback')({
'onPictureTaken': self._on_picture_taken
})
)
except Exception as e:
print(f"Error taking picture: {e}")
def _on_picture_taken(self, data, camera):
# data 是 JPEG 图片的字节数组
print("Picture taken!")
# 将字节数组转换为 Kivy 可以识别的路径或 base64 字符串
# 这里我们将其保存到应用的私有目录
from jnius import cast
from android.storage import app_storage_path
import os
# 获取应用内部存储路径
storage_path = app_storage_path()
image_path = os.path.join(storage_path, 'captured_photo.jpg')
# 将 Java 字节数组写入文件
FileOutputStream = autoclass('java.io.FileOutputStream')
fos = FileOutputStream(image_path)
fos.write(data)
fos.close()
# 更新 Kivy Image 控件以显示新图片
self.preview.source = image_path
# 重新开始预览
camera.startPreview()
def on_stop(self):
# 当应用关闭时,确保释放相机资源
if hasattr(self, 'camera') and self.camera:
self.camera.release()
self.camera = None
super(CameraApp, self).on_stop()
if __name__ == '__main__':
CameraApp().run()
第 2 步:配置 Buildozer (buildozer.spec)
Buildozer 是一个工具,可以将你的 Kivy 应用打包成 APK 文件。
- 在项目根目录创建
buildozer.spec文件。 - 你可以从 Kivy 官方文档 复制一个默认的
buildozer.spec文件,然后进行修改。
关键配置项如下:
[app] # (str) Title of your application= Python Camera App # (str) Package name package.name = pythoncameraapp # (str) Package domain (needed for android/ios packaging) package.domain = org.example # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas,ttf # (str) Application versioning (method 1) version = 0.1 # (list) Application requirements # comma eparated e.g. requirements = sqlite3,kivy requirements = python3,kivy,pyjnius # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # (string) Presplash background color (for new toolchain) # supported are: `slategray`, `gray`, `lightgray`, `white`, `red`, `green`, `blue`, `yellow`, `cyan`, `magenta`, `black`, `white` #android.presplash_color = white # (list) Permissions android.permissions = CAMERA, WRITE_EXTERNAL_STORAGE # 注意:从 Android 6.0 (API 23) 开始,CAMERA 和 WRITE_EXTERNAL_STORAGE 都是运行时权限, # 你的应用需要在运行时请求这些权限,上面的代码中省略了这部分,但实际应用中必须添加。 [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (command default)) log_level = 2 # (int) Display command output (0 = no, 1 = yes (command default)) # command_verbose = 1
重要: android.permissions = CAMERA, WRITE_EXTERNAL_STORAGE 是必须的,否则应用无法访问摄像头和保存照片。
第 3 步:构建 APK
- 确保你的电脑上安装了 Python 和 Buildozer,如果没有,全局安装:
pip install buildozer
- 将你的 Android 手机通过 USB 连接到电脑,并开启“USB 调试”模式。
- 在项目根目录(
buildozer.spec所在目录)打开终端,运行构建命令:buildozer android debug deploy run
android: 指定平台是 Android。debug: 构建一个调试版本。deploy: 将 APK 部署到连接的设备上。run: 在设备上启动应用。
首次运行会下载很多 SDK 和 NDK,速度可能很慢,请耐心等待,构建成功后,你的应用就会自动安装到手机上并启动。
使用 Chaquopy (更灵活,但更复杂)
这种方式适合那些想在一个成熟的 Android App 中集成 Python 脚本的开发者。
基本思路
- 创建 Android Studio 项目: 使用 Android Studio 创建一个新的或现有的项目。
- 集成 Chaquopy: 在
app/build.gradle文件中添加 Chaquopy 插件。 - 编写 Python 代码: 在
app/src/main/python目录下放置你的 Python 脚本。 - 编写 Java/Kotlin 代码: 在 Android Activity 中,通过 Chaquopy 提供的 API 加载和执行 Python 脚本,并将 Android 的相机对象传递给 Python。
- 交互:
- Java/Kotlin (Android): 获取
Camera实例,调用takePicture()。 - Java/Kotlin: 将图片数据(
byte[])作为参数传递给 Python 函数。 - Python: 接收数据,进行处理(应用滤镜、进行 AI 推理等)。
- Python: 将处理后的结果(新的
byte[]或文件路径)返回给 Java/Kotlin。 - Java/Kotlin: 将结果显示在
ImageView上或保存到存储。
- Java/Kotlin (Android): 获取
示例代码片段 (Java)
// 在你的 Android Activity 中
import com.chaquo.python.PyObject;
import com.chaquo.python.Python;
import android.hardware.Camera;
import ...
public class MainActivity extends AppCompatActivity {
private Camera camera;
private Python python;
private PyObject pythonModule;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化 Chaquopy
python = Python.getInstance();
pythonModule = python.getModule("camera_script"); // 假设你的Python文件叫camera_script.py
// ... 初始化相机逻辑 ...
}
public void onCaptureClick(View view) {
camera.takePicture(null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
// 将图片数据传递给 Python
PyObject result = pythonModule.callAttr("process_image", data);
// 假设 Python 返回处理后的图片字节数组
byte[] processedData = result.toJava(byte[].class);
// 在 UI 上显示结果
runOnUiThread(() -> {
ImageView imageView = findViewById(R.id.imageView);
Bitmap bitmap = BitmapFactory.decodeByteArray(processedData, 0, processedData.length);
imageView.setImageBitmap(bitmap);
});
// 重新开始预览
camera.startPreview();
}
});
}
}
对应的 camera_script.py 文件:
# app/src/main/python/camera_script.py
def process_image(image_data):
# 在这里你可以使用任何 Python 图像处理库,如 Pillow, OpenCV, scikit-image 等
from PIL import Image
import io
# 将字节数据转换为 PIL Image
pil_image = Image.open(io.BytesIO(image_data))
# 示例:将图片转为灰度
pil_image = pil_image.convert('L')
# 将处理后的 PIL Image 转回字节数据
output_stream = io.BytesIO()
pil_image.save(output_stream, format='JPEG')
processed_data = output_stream.getvalue()
return processed_data
总结与对比
| 特性 | Kivy + Pyjnius | Chaquopy |
|---|---|---|
| 开发模式 | 纯 Python 开发,UI 和逻辑在一起。 | Android 原生开发 + Python 脚本。 |
| 学习曲线 | 相对较低,如果你熟悉 Kivy。 | 较高,需要掌握 Android/Java 开发。 |
| 灵活性 | 适合开发完整的、独立的 Python 应用。 | 适合在现有 Android App 中嵌入 Python 功能。 |
| 性能 | 性能取决于 Kivy 和 Pyjnius 的桥接。 | Python 和 Java 的交互有开销,但适合特定任务。 |
| 打包 | 使用 Buildozer,过程相对自动化。 | 使用 Android Studio,需要手动配置 Gradle。 |
| 适用场景 | 快速原型、个人项目、想用 Python 开发完整 App。 | 将 Python 的强大库(如 AI/ML、科学计算)集成到 App 中。 |
对于初学者或者只想快速实现一个 Python 控制的相机应用,Kivy + Pyjnius 是更好的起点,如果你已经有了一个 Android App,并想在其中加入 Python 的数据分析或 AI 能力,Chaquopy 是不二之选。
