【代码解析】opencv 安卓 SDK sample - 1 - HDR image

很久没有写安卓了,复习复习。用的是官方案例,详见opencv-Android-sdk

java 复制代码
// 定义包名,表示该类的组织路径
package org.opencv.samples.tutorial1;

// 导入所需的OpenCV和Android类库
import org.opencv.android.CameraActivity;  // OpenCV相机活动基类
import org.opencv.android.CameraBridgeViewBase.CvCameraViewFrame;  // 相机帧数据
import org.opencv.android.OpenCVLoader;  // OpenCV加载器
import org.opencv.core.Mat;  // OpenCV矩阵类,用于存储图像数据
import org.opencv.android.CameraBridgeViewBase;  // 相机视图基类
import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener2;  // 相机监听接口
import org.opencv.android.Utils;  // OpenCV工具类
import org.opencv.core.Core;  // OpenCV核心功能
import org.opencv.imgproc.Imgproc;  // 图像处理模块
import org.opencv.photo.Photo;  // 照片处理模块
import org.opencv.core.*;  // OpenCV核心模块

// 导入Android相关类库
import android.os.Bundle;  // 用于保存Activity状态
import android.util.Log;  // 日志工具
import android.view.SurfaceView;  // 表面视图
import android.view.WindowManager;  // 窗口管理
import android.widget.Toast;  // 提示消息
import android.widget.Button;  // 按钮控件
import android.view.View;  // 视图基类
import android.os.Environment;  // 环境信息
import android.graphics.Bitmap;  // 位图类

// 导入Java工具类
import java.util.Collections;  // 集合工具
import java.util.ArrayList;  // 动态数组
import java.util.List;  // 列表接口
import java.io.File;  // 文件类
import java.io.FileOutputStream;  // 文件输出流
import java.text.SimpleDateFormat;  // 日期格式化
import java.util.Date;  // 日期类

Tutorial1Activity 类

定义

复制代码
public class Tutorial1Activity extends CameraActivity implements CvCameraViewListener2

继承自 CameraActivity ( 是Activity 基类,它封装了对摄像头的基本处理逻辑,比如打开摄像头、获取帧数据等。)

接口声明

OpenCV 摄像头帧回调接口 CvCameraViewListener2。

复制代码
implements CvCameraViewListener2

表示这个类 实现了 接口 CvCameraViewListener2,必须实现该接口的全部方法

这个接口是 OpenCV 提供的,用于处理来自摄像头的数据帧。它有几个关键方法:

复制代码
public interface CvCameraViewListener2 {
    void onCameraViewStarted(int width, int height);
    void onCameraViewStopped();
    Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame);
}
方法 作用
onCameraViewStarted() 摄像头启动时初始化 Mat、变量等
onCameraViewStopped() 摄像头停止时释放资源
onCameraFrame() 每帧图像自动调用,可以处理图像

📌 各方法说明:

onCameraViewStarted(int width, int height):摄像头视图开始时调用。

onCameraViewStopped():摄像头视图停止时调用。

onCameraFrame(CvCameraViewFrame inputFrame):每帧图像采集时调用,可以在这里进行图像处理(如边缘检测、滤波等),返回 Mat 类型图像结果用于渲染显示。

构造函数

复制代码
 // 构造函数
    public Tutorial1Activity() {
        // 打印日志,记录实例化信息
        Log.i(TAG, "Instantiated new " + this.getClass());
    }

定义

java 复制代码
    // 定义日志标签常量
    定义一个日志标签(log tag),用于在 Android 的 Logcat 控制台中打印日志。便于调试、排查错误,推荐每个类定义一个 TAG。
    private static final String TAG = "OCVSample::Activity";

    // 声明相机视图对象
    声明一个 OpenCV 的摄像头视图控件,它负责打开摄像头、采集图像帧、并把图像送入 onCameraFrame() 回调处理。
    封装了摄像头的打开、预览、帧捕获、图像格式转换等功能。
    CameraBridgeViewBase 是 OpenCV for Android 提供的一个抽象类,常用的子类是 JavaCameraView。
    private CameraBridgeViewBase mOpenCvCameraView;
    
    // 声明拍照按钮
    在 onCreate 中设置
    利用 setOnClickListener 触发
    private Button mCaptureButton;
    
    // 声明存储RGBA图像的矩阵
    onCameraFrame() 回调函数中,OpenCV 会将摄像头捕获的帧传递进来
    private Mat mRgba;

    // HDR相关声明
    private Button mHdrButton;  // HDR按钮
    private boolean mIsCapturingHDR = false;  // HDR拍摄标志位
    private List<Mat> mExposureSequence = new ArrayList<>();  // 存储不同曝光图像的列表
    private static final float[] EXPOSURE_VALUES = {0.5f, 1.0f, 2.0f};  // 定义三种曝光值

函数解析

在 Android 中,Activity 有一个完整的生命周期管理,典型调用顺序如下:

onCreate() → 创建视图和变量

onStart() → Activity 对用户可见

onResume() → Activity 可交互(获取焦点)

onPause() → 准备进入后台

onStop() → Activity 不再可见

onDestroy() → Activity 被销毁

Activity 的构造函数 onCreate

系统会先调用隐藏的构造函数,然后调用 onCreate()。 当该 Activity 第一次被创建时(即启动时),系统会自动调用它。到 onCreate() 时,系统已经准备好了界面容器、上下文环境(Context)、资源引用、生命周期管理器。

这时才能用 findViewById方法

在 onCreate() 中,通常执行以下操作:

  1. 设置界面布局
java 复制代码
setContentView(R.layout.activity_main);

加载 XML 中定义的 UI 界面。

  1. 初始化 UI 控件和变量
java 复制代码
mOpenCvCameraView = findViewById(R.id.java_camera_view);
mCaptureButton = findViewById(R.id.capture_button);

完成变量与界面元素的绑定,准备用户交互。

  1. 设置控件监听器
java 复制代码
mCaptureButton.setOnClickListener(v -> captureImage());

为按钮或其他 UI 元件设置回调函数。

  1. 初始化 OpenCV 或其他第三方库
java 复制代码
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION, this, mLoaderCallback);

加载 OpenCV 库并处理初始化回调。

java 复制代码
    // Activity创建时的回调方法
@Override 对父类 Activity 中的 onCreate 方法的重写。
Bundle savedInstanceState:
是一个用于存储界面状态的数据结构。
当 Activity 被意外销毁后(如屏幕旋转、被系统回收等),可以通过它恢复先前的状态。
    public void onCreate(Bundle savedInstanceState) {
    
        // 调用父类onCreate方法
        super.onCreate(savedInstanceState);
        // 打印日志
        Log.i(TAG, "called onCreate");

        // 初始化OpenCV本地库
        if (OpenCVLoader.initLocal()) {
            // 初始化成功日志
            Log.i(TAG, "OpenCV loaded successfully");
        } else {
            // 初始化失败日志
            Log.e(TAG, "OpenCV initialization failed!");
            // 显示初始化失败提示
            (Toast.makeText(this, "OpenCV initialization failed!", Toast.LENGTH_LONG)).show();
            return;
        }

        // 设置窗口标志,保持屏幕常亮
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        // 设置布局文件
        setContentView(R.layout.tutorial1_surface_view);

        // 获取相机视图引用
将 XML 布局文件中声明的 OpenCV 摄像头预览控件绑定到 Java 代码中的变量 mOpenCvCameraView 上,以便后续调用方法控制摄像头。
强制类型转换,因为 findViewById() 返回的是通用类型 View,而你希望将它当作 CameraBridgeViewBase 来使用。
CameraBridgeViewBase 是 OpenCV 提供的抽象基类,定义了摄像头启动、停止、帧获取等功能。
JavaCameraView 是它的子类,真正实现了预览逻辑。
        mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.tutorial1_activity_java_surface_view);
        // 获取拍照按钮引用
        mCaptureButton = (Button) findViewById(R.id.capture_button);
        // 获取HDR按钮引用
        mHdrButton = (Button) findViewById(R.id.hdr_button);

        // 设置相机视图可见性
        mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE);

        // 设置相机视图监听器
        mOpenCvCameraView.setCvCameraViewListener(this);

        // 设置拍照按钮点击监听器
        mCaptureButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 检查当前帧是否有效
                if (mRgba != null && !mRgba.empty()) {
                    // 调用保存图像方法
                    saveImage(mRgba);
                }
            }
        });

        // 设置HDR按钮点击监听器
        mHdrButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 设置HDR拍摄标志
                mIsCapturingHDR = true;
                // 清空曝光序列
                mExposureSequence.clear();
                // 显示提示信息
                Toast.makeText(Tutorial1Activity.this, "准备HDR拍摄,请保持手机稳定...",
                        Toast.LENGTH_SHORT).show();
            }
        });
    }

CameraBridgeViewBase 是什么?

虽然 CameraBridgeViewBase 本质上是一个"视图组件"(View),但它不仅仅是用来显示摄像头图像,还内置了摄像头控制逻辑、帧采集机制和 OpenCV 回调接口对接。

它是 OpenCV for Android 提供的一个抽象类,负责:

功能 说明
控制摄像头启动/停止 封装了打开摄像头、释放摄像头资源的流程
帧采集循环 自动开启后台线程采集图像帧
图像格式转换 将 NV21/YUV 等图像转换为 OpenCV 可处理的 Mat 类型
接入回调 让开发者通过接口拿到每一帧图像进行处理

帧采集 + 回调的完整机制

方法 功能
setCvCameraViewListener(this) 设置帧数据监听器,接收每一帧图像
enableView() 内部会打开摄像头,启动采集线程,开始不断生成帧

OpenCV 内部怎么做的?

OpenCV 封装了以下逻辑:

  • 打开摄像头(通过 Camera / Camera2 API)

    后台线程循环采集图像帧(YUV 或 RGBA)

    转换为 Mat 类型(OpenCV 可处理格式)

    调用你实现的接口方法:

java 复制代码
public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
    // 每帧图像都走这里,你可以处理它
    return inputFrame.rgba(); // 或处理后返回
}

对应

复制代码
你的代码
  ↓
CameraBridgeViewBase
  ↓
JavaCameraView / NativeCameraView
  ↓
系统 Camera API
  ↓
图像帧采集 → 自动转换为 Mat → 回调 onCameraFrame()
java 复制代码
    // Activity暂停时的回调方法
    @Override
    public void onPause() {
        // 调用父类方法
        super.onPause();
        // 如果相机视图存在,则禁用视图
        if (mOpenCvCameraView != null)
            mOpenCvCameraView.disableView();
    }

    // Activity恢复时的回调方法
    @Override
    public void onResume() {
        // 调用父类方法
        super.onResume();
        // 如果相机视图存在,则启用视图
        if (mOpenCvCameraView != null)
            mOpenCvCameraView.enableView();
    }

    // 获取相机视图列表的方法
    @Override
    protected List<? extends CameraBridgeViewBase> getCameraViewList() {
        // 返回包含单个相机视图的不可变列表
        return Collections.singletonList(mOpenCvCameraView);
    }

    // Activity销毁时的回调方法
    @Override
    public void onDestroy() {
        // 调用父类方法
        super.onDestroy();
        // 如果相机视图存在,则禁用视图
        if (mOpenCvCameraView != null)
            mOpenCvCameraView.disableView();
    }

    // 相机视图启动时的回调方法
    @Override
    public void onCameraViewStarted(int width, int height) {
        // 初始化RGBA矩阵
        mRgba = new Mat();
    }

    // 相机视图停止时的回调方法
    @Override
    public void onCameraViewStopped() {
        // 如果RGBA矩阵存在,则释放资源
        if (mRgba != null) {
            mRgba.release();
        }
    }

    // 处理相机帧数据的回调方法
    @Override
    public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
        // 获取当前帧的RGBA数据
        mRgba = inputFrame.rgba();
        // 返回处理后的帧
        return mRgba;
    }
    

保存文件

java 复制代码
// 保存图像到文件的方法
    private void saveImage(Mat mat) {
        // 创建与Mat相同尺寸的Bitmap对象
        Bitmap bmp = Bitmap.createBitmap(mat.cols(), mat.rows(), Bitmap.Config.ARGB_8888);
        // 将Mat转换为Bitmap
        Utils.matToBitmap(mat, bmp);

        // 创建时间戳格式
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        // 生成文件名
        String imageFileName = "IMG_" + timeStamp + ".jpg";

        // 获取公共图片目录
        File storageDir = Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES);
        // 创建文件对象
        File imageFile = new File(storageDir, imageFileName);

        try {
            // 创建文件输出流
            FileOutputStream fos = new FileOutputStream(imageFile);
            // 压缩Bitmap为JPEG格式并写入文件
            bmp.compress(Bitmap.CompressFormat.JPEG, 100, fos);
            // 关闭输出流
            fos.close();

            // 显示保存成功提示
            Toast.makeText(this, "照片已保存: " + imageFile.getAbsolutePath(),
                    Toast.LENGTH_LONG).show();
        } catch (Exception e) {
            // 记录保存失败日志
            Log.e(TAG, "保存照片失败", e);
            // 显示保存失败提示
            Toast.makeText(this, "保存照片失败", Toast.LENGTH_SHORT).show();
        }
    }
相关推荐
东风西巷3 分钟前
MolyCamCCD复古胶片相机:复古质感,时尚出片
android·数码相机·智能手机·软件需求
神经星星8 分钟前
【TVM 教程】在 TVM 中使用 Bring Your Own Datatypes
人工智能·深度学习·机器学习
说私域35 分钟前
虚拟与现实交融视角下定制开发开源AI智能名片S2B2C商城小程序赋能新零售商业形态研究
人工智能·小程序·开源·零售
她说人狗殊途39 分钟前
神经网络基础讲解 一
人工智能·深度学习·神经网络
阿里云大数据AI技术40 分钟前
【新模型速递】PAI-Model Gallery云上一键部署MiniMax-M1模型
人工智能·llm·云计算
胖墩会武术41 分钟前
【PyTorch项目实战】CycleGAN:无需成对训练样本,支持跨领域图像风格迁移
人工智能·pytorch·python
恋猫de小郭1 小时前
Flutter 里的像素对齐问题,深入理解为什么界面有时候会出现诡异的细线?
android·前端·flutter
老周聊大模型1 小时前
ReAct Agent终极指南|LangChain实战×多工具调度×幻觉消除(
人工智能·程序员
学术 学术 Fun1 小时前
Vui:轻量级语音对话模型整合包,让交互更自然
人工智能·深度学习·ai
liang_jy1 小时前
Java 线程实现方式
android·java·面试