【Android探索】基于OpenCV的微液滴粒径分析APP

前言:这个App是之前《数字图像处理》课程的一次课程设计中的产物,现在整理一下记录下来,里面涉及到了比较多的控件以及拓展包,功能不是很丰富但是也比较算齐全,其中使用的技术原理包括在安卓上使用OpenCV库对图像进行一些预处理、利用透视变换方法纠正歪斜图像、生成excel统计表格、以及做一些柱状图统计数据等。因为设计的控件等比较多,所以下文以功能模块的形式分块介绍,有需要的可以直接参考相关模块即可。

目录

 

一、相机拍照及从内存获取图片

二、OpenCV导入(免安装Opencv Manager)

三、图像倾斜纠正及局部裁剪

四、数据统计柱状图控件

五、数据统计导出excel表格

         六、总结


一、相机拍照及从内存获取图片

(1)调用系统摄像头开启相机拍照方式获取图片

       开启系统相机的功能主要通过Android API中的File、URI(Uniform Resource Identifier)和Intent类来实现。URI是统一资源定位符。在Android 7.0以下,通过调用URI的fromFile(File file)方法可以获取到参数中file文件对应的资源引用URI,用来定位到图片拍摄后放置的文件位置。而在Android 7.0以上加入了文件保护的功能,需要通过FileProvider类的getUriForFile(File file)方法来获取file对应的URI。考虑到Android版本的兼容性,这里封装为getMediaFileUri方法,代码如下所示:

    /**
     * @param activity
     * @Author: Linzs email:linzs.online@gmail.com
     * @Date: 2019/10/24
     * @Description: 打开系统摄像头并返回图片(处理了兼容性问题)
     */
    public void openCamera(Activity activity) {
        int currentapiVersion = Build.VERSION.SDK_INT;// 获取系統版本
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);// 申请调用系统相机
        // 判断存储卡是否可以用,可用进行存储
        if (hasSdcard()) {
            //设置一个日期格式
            SimpleDateFormat timeStampFormat = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
            //图片的命名为日期时间
            string_camera = timeStampFormat.format(new Date());
            tempFile = new File(Environment.getExternalStorageDirectory(),
                    string_camera + ".jpg");

            //判断系统版本号,兼容Android 7.0
            //如果系统版本号小于Android 7.0
            if (currentapiVersion < Build.VERSION_CODES.N) {
                // 从文件中创建uri
                imageUri = Uri.fromFile(tempFile);
                intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
            } else {
                //当系统版本号大于Android 7.0使用共享文件的形式
                ContentValues contentValues = new ContentValues(1);
                contentValues.put(MediaStore.Images.Media.DATA, tempFile.getAbsolutePath());
                //检查是否有存储权限,以免崩溃
                if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                        != PackageManager.PERMISSION_GRANTED) {
                    //申请内存写权限
                    Toast.makeText(this, "请开启存储权限", Toast.LENGTH_SHORT).show();
                    return;
                }
                imageUri = activity.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
                intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);


            }
        }
        // 开启一个带有返回值的Activity,请求码为CODE_TAKE_PHOTO
        activity.startActivityForResult(intent, CODE_TAKE_PHOTO);
    }

首先声明File文件的位置,通过getMediaFileUri方法获取到mediaFile文件对应的imageUri。然后声明一个调用系统拍照功能的takePhotoIntent,将对应的imageUri传入到takePhotoIntent的putExtra方法中进行传递。最后将takePhotoIntent传入到startActivityForResult中实现启动系统相机的拍照功能。其中onActivityResult函数中接收到请求码CODE_TAKE_PHOTO之后这样处理事件:

case CODE_TAKE_PHOTO:
                if (resultCode == RESULT_OK) {
                    Intent Camera_intent=new Intent(MainActivity.this,SmartCrop_Activity.class);
                    Camera_intent.setData(imageUri);
                    startActivityForResult(Camera_intent,PHONE_CODE_CUT);

                }
                break;

 

(2)直接读手机内存从相册中调出图片

同样的当触发从图库中调用照片时候,也开一个intent处理申请图片事件

    /**
     * @Author: Linzs email:linzs.online@gmail.com
     * @Date: 2019/10/24
     * @Description: 从图库中提取照片
     */
    public void openAlbum() {
        Intent intent = new Intent(Intent.ACTION_PICK);
        intent.setType("image/*");
        // 开启一个带有返回值的Activity,请求码为GET_PHOTO
        startActivityForResult(intent, CODE_GET_PHOTO);
    }

其中onActivityResult函数中接收到请求码CODE_GET_PHOTO之后这样处理事件: 

 case CODE_GET_PHOTO:
                if (resultCode == RESULT_OK) {
                    if (data != null) {
                        Uri uri = data.getData();
                        imageUri = uri;
                    }
                    Intent Camera_intent=new Intent(MainActivity.this,SmartCrop_Activity.class);
                    Camera_intent.setData(imageUri);
                    startActivityForResult(Camera_intent,PHONE_CODE_CUT);
                }
                break;

 

二、OpenCV导入(免安装Opencv Manager)

(1)下载OpenCV的Android支持包。

在OpenCV官网https://opencv.org/releases/支持库下载页面中,有很多不同版本的Android支持包。可以根据自己的需要选择不同的版本进行下载,本文选用的是3.4.8版本。下载完成的支持库是压缩包的形式,解压完成后可以看到目录下有两个配置文件以及sdk等三个文件夹,如图所示。

其中apk文件夹下提供的是相应版本的OpenCVManager,samples文件夹下提供的是OpenCV官方的一些例子,方便开发者进行学习,sdk文件下的才是我们需要的。在sdk文件下有etc、java、native三个文件夹和一个build.gradle文件。其中java文件夹中的内容是最终需要导入到Android应用程序中OpenCV封装的Java库(后文简称为OpenCV4Android库),记录下这个Java库所在路径,下面的步骤需要通过这个路径将OpenCV4Android库导进来。

 

(2)Android Studio引入OpenCV4Android库。

在Android Studio中新建一个工程,全部为默认设置即可。在新建好的工程中,点击“File --> New --> Import Module”导入OpenCV4Android库,然后选择OpenCV库的路径。Module name可以自由定义,本文自动识别为OpenCVLibrary348,然后Next --> Finish即可。

 

(3)修改模块OpenCVLibrary348中build.gradle文件的配置。

OpenCVLibrary341模块的build.gradle文件中的compileSDKVersion、builtToolsVersion、minSDKVersion、targetSDKVersion几个版本号需要修改为与app模块的build.gradle文件中的版本号一致,本文的app模块下build.gradle文件的compileSDKVersion、builtToolsVersion、minSDKVersion、targetSDKVersion分别为28、28.0.3、19、28。

修改后OpenCVLibrary348中build.gradle文件内容如下所示:

apply plugin: 'com.android.library'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"

    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 29
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
}

 

(4)为app主模块添加OpenCVLibrary348模块依赖。

点击“File --> Project Structure”打开项目结构。选中app模块,选择标签栏的“Dependencies”,点击右边“+”出现下拉框,在下拉框中选择“Module dependency”,即可看到OpenCVLibrary348,选中OpenCVLibrary348后完成整个OpenCV4Android库的导入以及配置,如图所示。

 

(5)设置免安装OpenCVManager。

OpenCVManager保存了为Java库提供底层支持的libOpenCV_java3.so库,因此我们只要手动地将libOpenCV_java3.so这个库添加到Android应用程序后即可实现相同的效果,就不需要额外再下载OpenCVManager管理应用了。

libOpenCV_java3.so放在图7中sdk文件下的native/libs文件夹,点开可以看到armeabi-v7a、armeabi、arm64-v8a、x8等多个不同的架构,每个架构文件夹下就是对应架构的libOpenCV_java3.so。

一般来说,市面上的Android手机基本都可以兼容armeabi-v7a架构,考虑到应用体积优化的问题,因此我们将armeabi-v7a文件夹下的libOpenCV_java3.so导入到应用中即可。为了兼容性的问题也可以全部导入进去。

Android Studio中切换到Android视图,在app目录下创建一个jniLibs文件夹,并在jniLibs下创建armeabi-v7a文件夹,将libOpenCV_java3.so等文件导入到armeabi-v7a文件夹中,如图所示。

最后在MainActivity.class文件中将libOpenCV_java3.so加载进来,初始代码onResume函数中这样写:

    public void onResume() {
        super.onResume();
        if (!OpenCVLoader.initDebug()) {
            Log.i("cv", "Internal OpenCV library not found. Using OpenCV Manager for initialization");
        } else {
            Log.i("cv", "OpenCV library found inside package. Using it!");
        }
    }

配置完成后即可在应用程序的工程下使用OpenCV4Android库中提供的API。

OpenCV for Android 3.0版本里,示例程序直接就可免OpenCV Manager的安装,所以不必使用包含JNI部分那种方法。上面onResume函数写法也是参照官方例子写法,方法的确可行。

 

三、图像倾斜纠正及局部裁剪

在这里我们是调用了一个github上很不错的开源库SmartCrop,它是一个基于机器学习 HED 网络优化的图片裁剪库。这里给出它的github地址:https://github.com/pqpo/SmartCropper

博主在他的个人博客上发布了使用该库的方法:https://pqpo.me/2019/08/02/machine-learning-hed-smartcropper/

SmartCropper 是一个智能图片裁剪框架,能够智能识别图片中的文档边框,自动瞄点选中文档边框,用户只需按下裁剪即可获得选取区放大图片。支持特性:

  1. 使用智能算法识别图片中的边框
  2. 支持拖动锚点,手动调节选区
  3. 使用透视变换裁剪并矫正选区

下面直接来说调用方法:

1、在这个库的github上下载最新的release压缩库

这里给出下载地址:https://github.com/pqpo/SmartCropper/releases/download/v2.1.1/smartcropperlib-v2.1.1-release.aar

下载好之后放入到~/app/libs目录下:

2、在根目录下的 build.gradle 添加如下code:

注意区分如下的各个build.gradle

这里是在Module:app中的bulid.gradle文件里面添加如下代码:

repositories {
    maven { url "https://jitpack.io" }
    flatDir {
        dirs 'libs'  //智能裁剪
    }
}

3、然后添加库依赖

dependencies {
	  compile 'com.github.pqpo:SmartCropper:v2.1.3'
}

到此该库已经添加入工程中了,下面进行调用即可,他是直接封装成一个控件了,直接使用控件然后调用相应的函数即可实现相应的功能,关于该库的详细介绍在它的github上有介绍https://github.com/pqpo/SmartCropper

4、裁剪布局文件

<me.pqpo.smartcropperlib.view.CropImageView
android:id="@+id/iv_crop"
android:layout_width="match_parent"
android:layout_height="match_parent" />

5、启动自动选区并进行透视变换纠正图像

这是是通过intent机制启动另外一个Activity的方式来进行图片裁剪,在图片获取完成之后即抛出了一个请求码PHONE_CODE_CUT

 startActivityForResult(Camera_intent,PHONE_CODE_CUT);

之后SmartCorp的Activity响应,下面给出相应的布局以及Activity的code:

先是Activity的:

package com.example.dropletanalysisapp;

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import androidx.appcompat.app.AppCompatActivity;

import java.io.FileNotFoundException;

import me.pqpo.smartcropperlib.SmartCropper;
import me.pqpo.smartcropperlib.view.CropImageView;

import static com.example.dropletanalysisapp.MainActivity.crop_bitmap;

public class SmartCrop_Activity extends AppCompatActivity implements View.OnClickListener {

    private CropImageView iv_crop;
    private Button btn_cancel;
    private Button btn_ok;
    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.smartcrop_activity);
        initView();
        SmartCropper();
    }

    private void initView() {
        iv_crop = (CropImageView) findViewById(R.id.iv_crop);
        iv_crop.setOnClickListener(this);

        btn_cancel = (Button) findViewById(R.id.btn_cancel);
        btn_ok = (Button) findViewById(R.id.btn_ok);

        btn_cancel.setOnClickListener(this);
        btn_ok.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_cancel:
                iv_crop.setCropPoints(null);
                finish();
                break;
            case R.id.btn_ok:
                crop_bitmap = iv_crop.crop();
                //iv_crop.setCropPoints(null);
                setResult(RESULT_OK);
                finish();
                break;
        }
    }

    /**
     * @Author: Linzs email:linzs.online@gmail.com
     * @Date: 2019/11/16
     * @Description: 调用智能裁剪库
     */
    private void SmartCropper() {
        Intent get_intent = getIntent();
        if (get_intent != null) {
            Uri uri = getIntent().getData();
            Bitmap slectBitmap = null;
            try {
                slectBitmap = BitmapFactory.decodeStream(
                        getContentResolver().openInputStream(uri));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            Point[] points = SmartCropper.scan(slectBitmap);
            iv_crop.setCropPoints(points);
            iv_crop.setImageToCrop(slectBitmap);
        }
    }
}

然后是布局文件的code:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/background_dark"
    android:orientation="vertical">

    <me.pqpo.smartcropperlib.view.CropImageView
        android:id="@+id/iv_crop"
        android:layout_width="match_parent"
        android:padding="20dp"
        android:layout_height="0dp"
        android:layout_weight="1"
        app:civShowEdgeMidPoint="true"
        app:civLineColor="@color/colorPrimary"
        app:civMagnifierCrossColor="@color/colorPrimaryDark"/>

    <LinearLayout
        android:id="@+id/ll_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        android:padding="10dp"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btn_cancel"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="取消"/>

        <Button
            android:id="@+id/btn_ok"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="确定"/>

    </LinearLayout>

</LinearLayout>

 布局效果如下图:

其实主要的是两条函数使用

1)设置待裁剪图片

ivCrop.setImageToCrop(selectedBitmap);

2)裁剪选区内的图片并做透视变换纠正倾斜

Bitmap crop = ivCrop.crop();

 经过SmartCorp处理之后的图片通过 Android API 中的 BitmapFactory 类来实现展示。BitmapFactory 的decodeStream 方 法 对裁 剪 完 成 或 相册 选 择 图 片 完成 后 返 回 的Uri 进行输入流解析,最终转换成图片返回到主Activity显示在相应区域,代码如下:

case PHONE_CODE_CUT:
                if (resultCode == RESULT_OK) {
                    PHOTO.setImageBitmap(crop_bitmap);
                    //getPhotoSize();
                    }
                break;

 

四、数据统计柱状图控件

这里的数据统计控件是选择使用了MPChart库,这是github上的一个开源流行库,功能丰富完善。下面说说使用流程:

1、添加github依赖

在app目录下的build.gradle中加上下面依赖

    implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'

2、layout中用法

    <com.github.mikephil.charting.charts.BarChart
        android:id="@+id/chart"
        android:layout_width="350dp"
        android:layout_height="220dp"
        android:background="#DFE4E6"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

3、在Activity中对控件进行初始化

其实就是配置控件的一些初始化参数,比如:x、y轴的范围等

private void initBarChart(BarChart barChart) {
        YAxis leftAxis;             //左侧Y轴
        YAxis rightAxis;            //右侧Y轴
        XAxis xAxis;                //X轴
        Legend legend;              //图例

        //setTitle("BarChartActivity");
        // 不显示右下角的description
        barChart.getDescription().setEnabled(false);
        //背景颜色
        barChart.setBackgroundColor(Color.WHITE);
        //不显示图表网格
        barChart.setDrawBarShadow(false);
        barChart.setHighlightFullBarEnabled(false);
        //不显示边框
        barChart.setDrawBorders(false);
        //设置动画效果
        barChart.animateY(1000, Easing.Linear);
        barChart.animateX(1000, Easing.Linear);
        /***XY轴的设置***/
        //X轴设置显示位置在底部
        xAxis = barChart.getXAxis();// 获取 x 轴
        xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);// 设置 x 轴显示位置
        xAxis.setDrawGridLines(false);// 取消 垂直 网格线
        xAxis.setLabelRotationAngle(0f);// 设置 x 轴 坐标字体旋转角度
        xAxis.setTextSize(10f);// 设置 x 轴 坐标字体大小
        xAxis.setAxisLineColor(Color.GREEN);// 设置 x 坐标轴 颜色
        //xAxis.setAxisLineWidth(10f);// 设置 x 坐标轴线宽
        //xAxis.setLabelCount(10);// 设置 x轴 的刻度数量
        //xAxis.setCenterAxisLabels(true);
        xAxis.setAxisMinimum(1);
        xAxis.setAxisMaximum(30f);
        xAxis.setLabelCount(15);


        leftAxis = barChart.getAxisLeft();// 获取 y轴
        rightAxis = barChart.getAxisRight();
        //保证Y轴从0开始,不然会上移一点
        leftAxis.setAxisMinimum(0f);//左边坐标
        rightAxis.setAxisMinimum(0f);
        /***图例 标签 设置***/
        legend = barChart.getLegend();
        legend.setForm(Legend.LegendForm.LINE);
        legend.setTextSize(11f);
        //显示位置
        legend.setVerticalAlignment(Legend.LegendVerticalAlignment.BOTTOM);
        legend.setHorizontalAlignment(Legend.LegendHorizontalAlignment.RIGHT);
        legend.setOrientation(Legend.LegendOrientation.HORIZONTAL);
        //是否绘制在图表里面
        legend.setDrawInside(false);
        /***数据 设置***/
        // 定义一个list来存储x和y数据
        ArrayList<BarEntry> yValues = new ArrayList<>();
        for (int x = 2; x < 30; x++) {
            int y = (int)(Math.random()*30);
            yValues.add(new BarEntry(x, y));
        }
        // y 轴数据集

        BarDataSet barDataSet = new BarDataSet(yValues,"雾滴粒径(mm)");

        BarData mbarData = new BarData(barDataSet);//直方图数据初始化
        barChart.setData(mbarData);//显示直方图
        barChart.invalidate();//刷新
    }

之后可以通过给list刷新数据达到动态改变表格的统计数据,这里简单给出一个实例,第一个入口参数是图表控件、第二个是X轴的数据、第三个是Y轴的数据。通过调用该函数即可达到改变图表上的数据。

private void BarChar_DataSet(BarChart mbarChart, int[] x, int[] y){
        ArrayList<BarEntry> y_Values = new ArrayList<>();
        for(int i=0; i<x.length; i++){
            y_Values.add(new BarEntry(x[i],y[i]));
        }
        BarDataSet barDataSet = new BarDataSet(y_Values, "雾滴数量");
        BarData mbarData = new BarData(barDataSet);//直方图数据初始化
        mbarChart.setData(mbarData);//显示直方图
        mbarChart.invalidate();//刷新
    }

 

五、数据统计导出excel表格

本次项目的需求是将应用内的数据导出为excel表格,这里我们也是使用了一个现成的库,下面讲下详细使用过程:

1、添加依赖包

implementation group: 'net.sourceforge.jexcelapi', name: 'jxl', version: '2.6.12'

2、编写excel工具类 ‘

新建一个类ExcelUtil如下:

写入如下内容:

package com.example.dropletanalysisapp;

import android.content.Context;
import android.widget.Toast;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import jxl.Workbook;
import jxl.WorkbookSettings;
import jxl.format.Colour;
import jxl.write.Label;
import jxl.write.WritableCell;
import jxl.write.WritableCellFormat;
import jxl.write.WritableFont;
import jxl.write.WritableSheet;
import jxl.write.WritableWorkbook;
import jxl.write.WriteException;

/**
 * @date 2019/2/14
 */

public class ExcelUtil {

    private static WritableFont arial14font = null;

    private static WritableCellFormat arial14format = null;
    private static WritableFont arial10font = null;
    private static WritableCellFormat arial10format = null;
    private static WritableFont arial12font = null;
    private static WritableCellFormat arial12format = null;
    private final static String UTF8_ENCODING = "UTF-8";


    /**
     * 单元格的格式设置 字体大小 颜色 对齐方式、背景颜色等...
     */
    private static void format() {
        try {
            arial14font = new WritableFont(WritableFont.ARIAL, 14, WritableFont.BOLD);
            arial14font.setColour(jxl.format.Colour.LIGHT_BLUE);
            arial14format = new WritableCellFormat(arial14font);
            arial14format.setAlignment(jxl.format.Alignment.CENTRE);
            arial14format.setBorder(jxl.format.Border.ALL, jxl.format.BorderLineStyle.THIN);
            arial14format.setBackground(jxl.format.Colour.VERY_LIGHT_YELLOW);

            arial10font = new WritableFont(WritableFont.ARIAL, 10, WritableFont.BOLD);
            arial10format = new WritableCellFormat(arial10font);
            arial10format.setAlignment(jxl.format.Alignment.CENTRE);
            arial10format.setBorder(jxl.format.Border.ALL, jxl.format.BorderLineStyle.THIN);
            arial10format.setBackground(Colour.GRAY_25);

            arial12font = new WritableFont(WritableFont.ARIAL, 10);
            arial12format = new WritableCellFormat(arial12font);
            //对齐格式
            arial10format.setAlignment(jxl.format.Alignment.CENTRE);
            //设置边框
            arial12format.setBorder(jxl.format.Border.ALL, jxl.format.BorderLineStyle.THIN);

        } catch (WriteException e) {
            e.printStackTrace();
        }
    }


    /**
     * 初始化Excel表格
     *
     * @param filePath  存放excel文件的路径(path/demo.xls)
     * @param sheetName Excel表格的表名
     * @param colName   excel中包含的列名(可以有多个)
     */
    public static void initExcel(String filePath, String sheetName, String[] colName) {
        format();
        WritableWorkbook workbook = null;
        try {
            File file = new File(filePath);
            if (!file.exists()) {
                file.createNewFile();
            } else {
                return;
            }
            workbook = Workbook.createWorkbook(file);
            //设置表格的名字
            WritableSheet sheet = workbook.createSheet(sheetName, 0);
            //创建标题栏
            sheet.addCell((WritableCell) new Label(0, 0, filePath, arial14format));
            for (int col = 0; col < colName.length; col++) {
                sheet.addCell(new Label(col, 0, colName[col], arial10format));
            }
            //设置行高
            sheet.setRowView(0, 340);
            workbook.write();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (workbook != null) {
                try {
                    workbook.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 将制定类型的List写入Excel中
     *
     * @param objList  待写入的list
     * @param fileName
     * @param c
     * @param <T>
     */
    @SuppressWarnings("unchecked")
    public static <T> void writeObjListToExcel(List<T> objList, String fileName, Context c) {
        if (objList != null && objList.size() > 0) {
            WritableWorkbook writebook = null;
            InputStream in = null;
            try {
                WorkbookSettings setEncode = new WorkbookSettings();
                setEncode.setEncoding(UTF8_ENCODING);

                in = new FileInputStream(new File(fileName));
                Workbook workbook = Workbook.getWorkbook(in);
                writebook = Workbook.createWorkbook(new File(fileName), workbook);
                WritableSheet sheet = writebook.getSheet(0);

                for (int j = 0; j < objList.size(); j++) {
                    DropletAnalysis circle = (DropletAnalysis) objList.get(j);
                    List<String> list = new ArrayList<>();
                    list.add(String.valueOf(circle.getRad()));
                    list.add(String.valueOf(circle.getNum()));

                    for (int i = 0; i < list.size(); i++) {
                        sheet.addCell(new Label(i, j + 1, list.get(i), arial12format));
                        if (list.get(i).length() <= 4) {
                            //设置列宽
                            sheet.setColumnView(i, list.get(i).length() + 8);
                        } else {
                            //设置列宽
                            sheet.setColumnView(i, list.get(i).length() + 5);
                        }
                    }
                    //设置行高
                    sheet.setRowView(j + 1, 350);
                }


                writebook.write();
                workbook.close();
                Toast.makeText(c, "导出Excel成功", Toast.LENGTH_SHORT).show();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (writebook != null) {
                    try {
                        writebook.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                }
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

        }
    }


}

3、然后在主Activity里面调用即可

我这里是直接封装成一个函数来调用了

   /**
     * @Date: 2019/11/21
     * @Description: 写入excel
     */
    private void exportExcel(int[] circle_size, int circle_num){
        Context context=getApplicationContext();
        List<DropletAnalysis> drops=new ArrayList<>();
        File file = new File(filePath);
        if (!file.exists()) {
            file.mkdirs();
        }
        String excelFileName = "雾滴分析";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String time = sdf.format(new Date());//Calendar.getInstance().toString();
        String fileName = file.toString() + "/"+excelFileName+"-"+time+".xls";

        String[] title = {"半径", "数量"};
        String sheetName = "雾滴分析";
        for(int i=0;i<circle_num;i++) {
            DropletAnalysis circle = new DropletAnalysis(circle_size[i], i);
            drops.add(circle);
        }

        filePath = fileName;
        ExcelUtil.initExcel(filePath, sheetName, title);
        ExcelUtil.writeObjListToExcel(drops, filePath, context);
    }

关于设置文件名称、格式、标题等等详细看里面函数即可见名思义了。

 

六、总结

本次的Android探索是本人大学时期的一次课程设计,本人非计算机类专业,对Android也是一知半解,本次设计的App意义是做一个为了安装在手机上方便携带的喷施雾滴粒径分析仪,效果一般,当时一种课外学习。初次接触Android这门学科,其中还有很多掌握不到位的,通过这次学习掌握了一个App设计的大致流程以及一些基础Android控件和Java语法的使用。对于OpenCV部分函数在此不做展开,网上其他文章有更多详细的资料,OpenCV部分用法和PC端使用大致相同,调用相关API即可对图像做预处理。个人水平有限,文章中细节以及不足之处欢迎多多指正。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值