- OpenCV Android开发实战
- 贾志刚
- 5679字
- 2023-07-26 14:52:39
第1章 OpenCV Android开发框架
在开始本书内容之前,笔者假设大家已经有了面向对象语言编程的基本概念,了解了Java语言的基本语法与特征,并且尝试过Android平台上的应用程序开发。本章将主要介绍OpenCV的历史与发展、各个模块的功能说明、如何使用Android Studio IDE来建立OpenCV的开发环境,以及如何整合配置并成功运行和调用OpenCV中的函数实现一个最简单的OpenCV程序演示。如果没有特别说明,那么这里使用的OpenCV版本都是基于OpenCV 3.3 Android SDK。
作为使用最为广泛的计算机视觉开源库,OpenCV在开源社区与英特尔、谷歌等大公司的共同努力之下,发展到今天,已经吸引了全世界各地的开发者编译和使用它实现各种应用程序。而伴随着人工智能时代的到来,作为人工智能眼睛的计算机视觉必然会进一步释放活力,满足市场需要。OpenCV作为计算机视觉开源框架,其在移动端支持Android系统的特性必将进一步深入到移动开发的各种应用场景之中,下面就来开启一段OpenCV学习旅程。
1.1 OpenCV是什么
OpenCV的中文全称是源代码开放的计算机视觉库(Open Source Computer Vision Library),是基于C/C++编写的,是BSD开源许可的计算机视觉开发框架,其开源协议允许在学术研究与商业应用开发中免费使用它。OpenCN支持Windows、Linux、Mac OS、iOS与Android操作系统上的应用开发。在笔者动笔写这本书的时候,其最新版本3.3刚刚发布不久。
1.1.1 OpenCV的历史与发展
在OpenCV孕育发展的过程中,Intel公司做出了巨大的贡献,OpenCV最初是Intel公司的内部项目,随着时间的推移、OpenCV的功能算法得到不断的优化与增强,不过是短短十几年的时间,其已经席卷整个业界,得到众多著名IT公司的大力支持,其中包括大名鼎鼎的机器人公司Willow Garage与搜索引擎公司Google。下面的时间节点对OpenCV的发展都产生过重要影响,具体如下。
❑ 1999年,OpenCV正式立项,那个时候Android智能手机的春天还没有到来。
❑ 2000年,在IEEE的计算机视觉与模式识别大会上OpenCV正式发布Alpha版本。
❑ 2001年~2005年,Intel公司陆续发布了最初的5个Beta测试版本。
❑ 2006年,OpenCV1.0版本正式发布,基于C语言接口SDK调用。
❑ 2008年,OpenCV获得了当时发展如日中天的机器人公司Willow Garage的支持、得到了进一步推广,然而不幸的是,作为机器人业界的传奇公司Willow Garage却在2014宣布倒闭。
❑ 2009年,OpenCV2.0版本正式发布,这是OpenCV发展史上的一个重要里程碑,早期的OpenCV是基于C语言实现的,在2.0的版本中又添加了C++接口,并且对原来的C语言代码进行了优化和整合,以期吸引更多的开发者用户。
❑ 2012年,Intel公司决定把OpenCV开发者社区正式交给开源社区opencv.org运营与维护。
❑ 2014年,OpenCV3.0版本发布。
❑ 2016年,OpenCV3.1与OpenCV3.2版本相继发布,其扩展模块支持集成Google TensorFlow深度学习框架。
❑ 2017年,OpenCV3.3.x版本发布,在Release开发包中增加了DNN(深度神经网络)模块支持。
OpenCV支持Java语言开发的Android SDK最早是始于2010年。在OpenCV3.x版本中,OpenCV更加强调对移动端与嵌入式设备的支持。
(1)编程语言
OpenCV中的这些模块大多数都是基于C/C++完成的,少量的SDK接口模块使用Java、Python等语言开发。在最新开发的OpenCV的核心模块中,C++替代C成为了开发语言。
(2)应用领域
OpenCV自从1.0版本发布以来,立刻吸引了许多公司的目光,被广泛应用于许多领域的产品研发与创新上,相关应用包括卫星地图与电子地图拼接,医学中图像噪声处理、对象检测,安防监控领域的安全与入侵检测、自动监视报警,制造业与工业中的产品质量检测、摄像机标定,军事领域的无人机飞行、无人驾驶与水下机器人等众多领域。
1.1.2 OpenCV模块介绍
OpenCV分为正式的发布版本与扩展模块,Android SDK所对应的是OpenCV的发布版本,其扩展模块的功能可以通过源代码编译的方式进行集成与开发,关于扩展模块的编译与使用已经超出了本书的讨论范围,这里就不再赘述了。下面以OpenCV3.3为例,OpenCV正式发布版本中包含的核心功能模块具体如下。
❑ 二维与三维特征工具箱
❑ 运动估算
❑ 人脸识别
❑ 姿势识别
❑ 人机交互
❑ 运动理解
❑ 对象检测
❑ 移动机器人
❑ 分割与识别
❑ 视频分析
❑ 运动跟踪
❑ 图像处理
❑ 机器学习
❑ 深度神经网络
除上所述的核心功能模块之外,其扩展模块更加的庞大与繁杂。OpenCV Android SDK可以从其官方主页上下载获得,下载地址为:http://opencv.org/opencv-3-3.html,在最下面就可以发现Android SDK的下载链接,点击就可以直接去相关页面上下载最新的Android SDK。
1.1.3 OpenCV Android SDK
OpenCV Android SDK本质上是使用Java语言接口通过JNI技术调用OpenCV C/C++代码完成的算法模块。OpenCV4Android本身并不是一个纯Java语言的计算机视觉库,而是基于OpenCVC++本地代码、通过Java语言接口定义,基于JNI技术实现调用C++本地方法的SDK开发包。所以当你下载好OpenCV Android SDK之后,在它的SDK目录下可以看到如图1-1所示的目录结构。
图1-1
其中,etc目录里面有两个文件夹,里面都是一些XML数据文件,这些XML数据是训练好的HAAR与LBP级联分类器数据;java目录里面是Android SDK相关文件;native里面则是基于C/C++编译好的OpenCV Android平台支持的本地库文件、JNI层开发所需要的头文件及cmake文件,其中库文件大多数以*.a和*.so结尾。而在与SDK同层级的samples目录中则包含了OpenCV Android SDK的一些应用案例教程,以供初学者参考,但是很不幸的是,直到今天为止,这些教程仍然还是基于Eclipse开发环境来演示OpenCV功能,不得不说这是一个小小的缺憾,希望OpenCV社区在后面的Open CV版本中能够更新这些教程,使其基于Android Studio来演示。
此外,OpenCV Android SDK的功能与OpenCV对应发布版本中的功能完全相同,唯一不同的是因为Java语言的关系,Java层封装的接口的参数传递和方法调用,与C++的接口相比有一些差异,这些都是为了更适应Java语言的特性而做出的改动,使得Android开发者更加容易学习与使用OpenCV来解决问题。
1.2 OpenCV Android开发环境搭建
当OpenCV遇到Android时,两者就通过Java SDK或者Android NDK很好地结合在一起了,可是对于广大Android开发者或者OpenCV开发者来说,要想成功地在Android Studio上运行一个类似于Hello World的OpenCV程序,还需要做一些工作,下面就一起来完成这些工作,实现开发环境的搭建。
1.2.1 软件下载与安装
在搭建开发环境之前,首先需要下载和安装如下几个软件开发包。
❑ OpenCV Android SDK 3.3版本
❑ JDK8:64位
❑ Android Studio
❑ Android SDK与NDK开发包
这里需要特别说明一下的是,首先应该安装好JDK,之后再下载安装其他的软件开发包,全部下载安装完毕之后,就可以打开Android Studio——Android集成开发环境(IDE),配置好Android SDK的路径之后,Android Studio(IDE)工具就可以正常使用了。
所下载的OpenCV Android SDK 3.3是一个安装包,只要解压缩到指定磁盘即可,双击解压缩好的目录就可以看到1.1.3节中提到的几个目录与层次结构。在本书最后的案例开发中会涉及与使用NDK的开发包,这里暂时只要将其安装好即可。
版本问题
使用Android Studio与Android SDK、NDK开发时,同样的代码在不同的版本上运行可能会出现一些兼容性问题。因为项目实际需要或者个人偏好,大家使用的版本可能不尽相同,这里说一下本书所用的版本,具体如下。
❑ Android Studio 3.0
❑ Android SDK 26
❑ Android NDK r13b
1.2.2 环境搭建
环境搭建的整个过程可以分为如下四步。
1.新建Android项目
打开Android Studio IDE,选择【File】→【New Project... 】,结果如图1-2所示。
图1-2
把项目默认名称修改为OpencvDemo,然后点击【Next】按钮,结果如图1-3所示。
图1-3
这里支持的最小版本是Android 14(Android 4.0版本),继续点击【Next】按钮,结果如图1-4所示。
图1-4
默认选择Emtpy Activity,点击【Next】→【Finish】,之后就会得到一个新建的默认Android版本的Hello World程序,如果一切顺利的话,就可以真机运行,查看效果。
2.导入OpenCV Android SDK依赖项
选择【File】→[New...]→【Import Module... 】,打开对话框之后,选择解压缩好的OpenCV Android SDK目录中的sdk\java,模块名称会自动显示出当前OpenCV的版本信息,如图1-5所示。
图1-5
点击【Next】→【Finish】,完成导入。然后再选择【File】→【Project Structure...】打开依赖项添加对话框,选择最右侧的【+】按钮,完成添加之后如图1-6所示。
图1-6
点击【OK】按钮,结束。
3.复制本地依赖项OpenCV库文件
把目录结构导航从【Android】切换到【Projects】,如图1-7所示。
图1-7
选择app下面的libs,然后把OpenCV Android SDK目录native\libs下面的所有文件与文件夹全部复制到libs中去,最后删除所有以*.a结尾的文件。
4.修改Gradle脚本与编译
在Android Studio中双击打开如下两个Gradle脚本,如图1-8所示。
图1-8
把两个脚本中的minSdkVersion修改为14、targetSdkVersion修改为26,然后保存,如图1-9所示。
图1-9
在Module:app对应的build.gradle脚本中添加如下内容:
task nativeLibsToJar(type: Jar, description: 'create a jar archive of the native libs') { destinationDir file("$buildDir/native-libs") baseName 'native-libs' from fileTree(dir: 'libs', include: '**/*.so') into 'lib/' } tasks.withType(JavaCompile) { compileTask -> compileTask.dependsOn(nativeLibsToJar) }
然后在编译片段添加如下代码:
implementation fileTree(dir: "$buildDir/native-libs", include: 'native-libs. jar')
保存最终修改好的Gradle文件即可。选择【build】→【clean project】,之后再选择【rebuild project】就完成了整个环境变量的配置与编译。可是环境变量配置得是否正确,我们还不能做到心中有数,所以下面通过一个简单的测试程序来验证一下环境配置。
1.2.3 代码测试
为了验证Android Studio的环境配置是否正确,需要调用一下OpenCV的相关API,把一张彩色图像转换为灰度图像,借此来验证Android平台上的OpenCV SDK是否可以正确调用。因此首先要实现图像显示,在Android中,可以通过XML(activity_main. xml)配置文件选择ImageView元素来实现,还需要一个按钮来响应用户操作,这可以通过添加Button元素来实现。在RelativeLayout中,两个元素对应的XML显示如下:
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/process_btn" android:text="灰度"/> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitCenter" android:id="@+id/sample_img" android:src="@drawable/lena" android:layout_centerInParent="true"/>
在代码层面实现图像资源的加载,然后交给OpenCV处理,之后的返回结果显示可以由如下3个步骤来实现。
1)首先加载OpenCV的本地库,代码如下:
private void iniLoadOpenCV() { boolean success = OpenCVLoader.initDebug(); if(success) { Log.i(CV_TAG, "OpenCV Libraries loaded..."); } else { Toast.makeText(this.getApplicationContext(), "WARNING: Could not load OpenCV Libraries! ", Toast.LENGTH_LONG).show(); } }
2)对按钮加上OnClickListener事件响应,代码如下:
Button processBtn = (Button)this.findViewById(R.id.process_btn); processBtn.setOnClickListener(this);
3)在事件响应方法进行处理并显示结果,代码如下:
@Override public void onClick(View v) { Bitmap bitmap = BitmapFactory.decodeResource( this.getResources(), R.drawable.lena); Mat src = new Mat(); Mat dst = new Mat(); Utils.bitmapToMat(bitmap, src); Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGRA2GRAY); Utils.matToBitmap(dst, bitmap); ImageView iv = (ImageView)this.findViewById(R.id.sample_img); iv.setImageBitmap(bitmap); src.release(); dst.release(); }
需要注意的是,在MainActivity中,需要完成接口View.OnClickListener,这样才能保证按钮事件的正确响应。可以在手机上看到运行完整代码的效果。
注意:书中所有完整的源代码都可以在Github上下载,强烈建议运行每个源代码实例,将源代码看作本书的一部分。
1.3 构建演示APP
本节将尝试构建一个用来演示本书所讲内容的APP,希望在其中可以按章节来索引各章节的相关功能演示。在UI设计层面,首先需要设计一个流程,实现从主界面选择各章,然后到对应的各节的代码演示。该流程如图1-10所示。
图1-10
根据上述的流程可知,我们所需要的界面功能与元素如表1-1所示。
表 1-1
根据表1-1所述的UI设计与程序流程,在启动程序之后,首先选择所在章节,然后选择相关的演示程序进行查看,这样的APP结构有利于集成每章相关的演示程序。根据1.2节的内容,我们首先需要在layout目录下创建两个XML文件作为界面2与界面3,然后还需要将activity_main.xml文件中添加的ImageView与Button元素移到界面3中,在activity_main.xml中添加一个ListView元素作为界面1。这一切做好之后,再来看一下activity_main.xml中的ListView XML显示:
<ListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/chapter_listView"/>
做好了XML界面编程之后,需要创建如图1-11所示的几个类与包,它们之间的关系通过类图显示。
图1-11
图1-11中各个类的功能说明具体如下。
❑ MainActivity:第一个界面,显示本书的10个章标题列表。
❑ SectionsActivity:第二个界面,显示各章对应的演示程序。
❑ CharpteFrist1Activity:第三个界面,这里是第1章的演示程序,以后各章会创建属于自己的演示程序。
❑ ItemDto:列表的每个条目对应的数据类。
❑ ChapterUtils:工具类,可以获取章节列表。
❑ AppConstants:常量接口,定义各个章节与演示程序的名称。
❑ SectionsListViewAdaptor:Android ListView元素对应的数据模型。
首先要实现ListView的显示且选择事件响应,在事件响应中实现View跳转到指定页面。其中ListView的初始化代码如下:
private void initListView() { ListView listView = (ListView) findViewById(R.id.chapter_listView); final SectionsListViewAdaptor commandAdaptor = new SectionsListViewAdaptor (this); listView.setAdapter(commandAdaptor); commandAdaptor.getDataModel().addAll(ChapterUtils.getChapters()); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<? > parent, View view, int position, long id) { ItemDto dot = commandAdaptor.getDataModel().get(position); goSectionList(dot); } }); commandAdaptor.notifyDataSetChanged(); }
以第一个界面到第二个界面的跳转为例,实现跳转的代码如下:
private void goSectionList(ItemDto dto) { Intent intent = new Intent(this.getApplicationContext(), SectionsActivity. class); intent.putExtra(AppConstants.ITEM_KEY, dto); startActivity(intent); }
实现从第二个Activity(SectionActivity)跳转到各个演示程序的功能,首先需要在onCreate方法中对每章内容的演示程序实现ListView显示与选择监听,这部分的代码如下:
private void initListView(ItemDto dto) { ListView listView = (ListView) findViewById(R.id.secction_listView); final SectionsListViewAdaptor commandAdaptor = new SectionsListViewAdaptor (this); listView.setAdapter(commandAdaptor); commandAdaptor.getDataModel().addAll(ChapterUtils.getSections((int)dto. getId())); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<? > parent, View view, int position, long id) { String command = commandAdaptor.getDataModel().get(position). getName(); goDemoView(command); } }); commandAdaptor.notifyDataSetChanged(); }
然后在goDemoView方法中根据章节内容跳转到不同的演示程序Activity中,跳转到第1章的演示程序代码如下:
if(command.equals(AppConstants.CHAPTER_1TH_PGM_01)) { Intent intent = new Intent(this.getApplicationContext(), CharpteFrist1Activity.class); startActivity(intent); }
以后针对各章的内容,在此处添加相关的代码即可,这样就实现了代码的集成。本章完整的源代码可以参见上述提到的源代码文件。
1.4 拍照与图像选择
1.3节中,我们成功地构建了一个演示本书内容的APP框架,这里还需要对其进行进一步的细化,因为我们发现该框架还没有拍照或者图像选择功能,无法提供测试图像来演示OpenCV代码的功能,所以本节在1.3节所示代码的基础之上,再加上拍照与图像选择的功能。在Android系统中显示图像,早期一直有一个很大的问题,尤其是对于大的Bitmap对象,常常会因为DVM内存的问题导致OOM(Out of Momery)错误,开玩笑地说,做Java与Android开发如果没有遇到类似的问题,你出门都不好意思跟人打招呼。谷歌官方的做法是通过降采样使用Bitmap的微缩版图像,这里对选择的图像或者拍照所得图像的显示与处理依然沿用此策略。
1.拍照
调用Android拍照功能,拍照并返回图像,代码如下:
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); fileUri = Uri.fromFile(getSaveFilePath()); intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); startActivityForResult(intent, REQUEST_CAPTURE_IMAGE);
2.图像选择
调用Android图片浏览功能,选择一张图片,自动返回图像并显示,代码如下:
Intent intent = new Intent(); intent.setType("image/*"); intent.setAction(Intent.ACTION_GET_CONTENT); startActivityForResult(Intent.createChooser(intent, "图像选择..."), REQUEST_ CAPTURE_IMAGE);
3.加载大小合适的图像
首先获取图像的大小,然后得到图像的降采样版本,显示在ImageView对象元素中,杜绝OOM问题的发生,代码如下:
if(fileUri == null) return; ImageView imageView = (ImageView)this.findViewById(R.id.sample_img); BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(fileUri.getPath(), options); int w = options.outWidth; int h = options.outHeight; int inSample = 1; if(w > 1000 || h > 1000) { while(Math.max(w/inSample, h/inSample) > 1000) { inSample *=2; } } options.inJustDecodeBounds = false; options.inSampleSize = inSample; options.inPreferredConfig = Bitmap.Config.ARGB_8888; Bitmap bm = BitmapFactory.decodeFile(fileUri.getPath(), options); imageView.setImageBitmap(bm);
4.处理与显示
调用OpenCV4Android的API对图像进行有目的的处理,处理之后返回处理后的图像并显示。这里我简单地改写一下前面的OpenCV测试程序,使用OpenCV的imread功能来读取图像,完成灰度转换并显示,代码如下:
Mat src = Imgcodecs.imread(fileUri.getPath()); if(src.empty()) { return; } Mat dst = new Mat(); Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGR2GRAY); Bitmap bitmap = grayMat2Bitmap(dst); ImageView iv = (ImageView)this.findViewById(R.id.sample_img); iv.setImageBitmap(bitmap); src.release(); dst.release();
在拍照或者选择图像结束之后,回调onActivityResult()方法,对于照片,可以直接获得文件路径,而对于选择图像,则需要在回调处理中根据图像ID得到相关图像的正确文件路径。此外,这里还涉及后续章节中要详细解释的Mat对象与Bitmap对象。
本书后续章节的每个演示程序基本上都是基于这个顺序来实现的。而且如果没有特别的说明与支持,那么本书所有的图像都是RGB色彩空间、相关的拍照与图像选择代码会在本书后续章节中重复使用,后续章节针对这部分代码将不再重复讲述,主要的篇幅会放在OpenCV4Android相关知识的讲解与学习上。
注意:这里把加载OpenCV库文件放到了主界面对应的onCreate方法中去调用,这样就避免了到处加载OpenCV库文件的烦恼,同时有助于减少程序的代码量。
1.5 小结
本章主要介绍了OpenCV的历史与发展、相关功能模块、开发环境搭建,以及为了后续更好地学习本书知识所做的一些必要的Android知识铺垫、测试程序框架设计与编码实现。有了这些基础,读者才可以更好地学习后续章节所讲的每个知识点,并通过程序与代码来演示应用。
同时,通过本章的学习,即使是之前没有接触过OpenCV与Android编程的读者也对二者会有基本的感性认知,对OpenCV、Android SDK、如何集成配置Android Studio、如何开发与运行代码都有一定的了解与接触,为后续熟练使用IDE来开发使用OpenCV4Android打下牢固基础。希望大家通过本章的学习,可以顺利搭建好开发环境,运行好测试程序,为后续学习打下坚实基础。