杰瑞科技汇

Python如何实现Android拍照功能?

  1. 使用 Kivy + Pyjnius: 在一个完整的 Python GUI 框架(Kivy)中,通过一个桥梁(Pyjnius)调用 Android 的 Java/Kotlin 原生代码来访问摄像头。
  2. 使用 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 来控制摄像头。

准备工作

  1. 安装 Python 环境: 你需要一个能在 Android 上运行 Python 的环境,最常用的是 Termux,在手机上从 F-Droid 或 Google Play 安装 Termux。
  2. 安装 Kivy 和 Pyjnius: 在 Termux 中运行以下命令:
    pkg update && pkg upgrade
    pkg install python
    pip install kivy
    pip install pyjnius
  3. 准备 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 文件。

  1. 在项目根目录创建 buildozer.spec 文件。
  2. 你可以从 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

  1. 确保你的电脑上安装了 Python 和 Buildozer,如果没有,全局安装:
    pip install buildozer
  2. 将你的 Android 手机通过 USB 连接到电脑,并开启“USB 调试”模式。
  3. 在项目根目录(buildozer.spec 所在目录)打开终端,运行构建命令:
    buildozer android debug deploy run
    • android: 指定平台是 Android。
    • debug: 构建一个调试版本。
    • deploy: 将 APK 部署到连接的设备上。
    • run: 在设备上启动应用。

首次运行会下载很多 SDK 和 NDK,速度可能很慢,请耐心等待,构建成功后,你的应用就会自动安装到手机上并启动。


使用 Chaquopy (更灵活,但更复杂)

这种方式适合那些想在一个成熟的 Android App 中集成 Python 脚本的开发者。

基本思路

  1. 创建 Android Studio 项目: 使用 Android Studio 创建一个新的或现有的项目。
  2. 集成 Chaquopy: 在 app/build.gradle 文件中添加 Chaquopy 插件。
  3. 编写 Python 代码: 在 app/src/main/python 目录下放置你的 Python 脚本。
  4. 编写 Java/Kotlin 代码: 在 Android Activity 中,通过 Chaquopy 提供的 API 加载和执行 Python 脚本,并将 Android 的相机对象传递给 Python。
  5. 交互:
    • Java/Kotlin (Android): 获取 Camera 实例,调用 takePicture()
    • Java/Kotlin: 将图片数据(byte[])作为参数传递给 Python 函数。
    • Python: 接收数据,进行处理(应用滤镜、进行 AI 推理等)。
    • Python: 将处理后的结果(新的 byte[] 或文件路径)返回给 Java/Kotlin。
    • Java/Kotlin: 将结果显示在 ImageView 上或保存到存储。

示例代码片段 (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 是不二之选

分享:
扫描分享到社交APP
上一篇
下一篇