androidopencv教程
‘壹’ 濡备綍鍦╩ac绯荤粺涓娄娇鐢╫pencv for android
銆銆mac绯荤粺鍦╬ython涓婂畨瑁呴厤缃畂pencv镄勬ラわ细 銆銆1 涓嬭浇链鏂扮増OpenCV for Linux/Mac婧愭枃浠讹纴鐩鍓岖増链鏄2.4.3銆备笅杞藉悗瑙e帇銆 銆銆2. 铡 涓嬭浇链鏂扮増cmake锛.dmg鏂囦欢锛岀洰鍓岖増链鏄2.8.10.2銆备笅杞藉悗瀹夎呫 銆銆3. 鎺ヤ笅𨱒ュ紑濮嫔畨瑁卭pencv 銆銆寮涓涓缁堢锛岃繘鍏opencv瑙e帇缂╃殑鐩褰曪纴濡俢d 铹跺悗鎶婃枃浠跺す𨰾借繘铡汇 銆銆mkdir release 銆銆cd ./release 銆銆cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -D BUILD_PYTHON_SUPPORT=ON -D BUILD_EXAMPLES=ON .. 銆銆make 銆銆sudo make install 銆銆4. 镊虫opencv宸茬粡瑁呬笂浜嗐
‘贰’ Android OpenCV(二十三):Sobel算子边缘检测
Sobel算子在计算机视觉中扮演着边缘检测的重要角色。它通过加权计算图像中每个像素周围上下左右的灰度值差异,尤其在边缘区域,其结果会达到峰值,从而有效地识别边缘。索贝尔算子本质上是一种离散差分算子,用于估算亮度函数的梯度近似值,给出每个像素的梯度矢量或其法矢量。这个算子不仅效果显着,还能在一定程度上平滑噪声,但可能产生较为粗糙的边缘,且有时会出现伪边缘现象。
索贝尔算子使用两组3x3的矩阵,分别进行水平和垂直方向的滤波,其数学表达式为:若原始图像为A,经处理后的横向和纵向边缘图像分别记为Gx和Gy。计算每个像素的梯度大小和方向的公式如下:
当计算出的梯度方向θ为零时,意味着图像中的边缘是纵向的,即图像的左侧比右侧暗。
在实际应用中,函数中的第三个、第四个和第五个参数对于边缘检测效果至关重要。这些参数的关系是,任何方向的差分阶数都必须小于滤波器尺寸,特殊情况是当ksize=1时,需保证任意方向的阶数小于3。一般而言,如果差分阶数最大值为1,滤波器尺寸为3;最大值为2,滤波器尺寸为5;最大值为3,滤波器尺寸为7。当ksize为1时,滤波器会变为非正方形,如3x1或1x3。最后三个参数,通常情况下保持默认值,除非有特定需求。
‘叁’ Android项目引入OpenCV和FFmpeg
在安卓项目中引入OpenCV和FFmpeg库,本文以OpenCV-4.5.1和FFmpeg-4.4版本为例,详述引入过程。引入库需遵循以下步骤:
1. **新建Android C++工程**
2. **下载OpenCV-Android-SDK**
3. **导入OpenCV-SDK至Android工程**
4. **修改OpenCV库的SDK版本**
5. **APP引入OpenCV依赖**
6. **复制库文件至指定目录**
7. **编辑CMakeList.txt**
8. **相关学习资源**
9. **初始化OpenCV**
引入库后会面临安装包体积增大的问题,以及使用JNI时的C++学习需求。在引入库过程中,遇到错误时应耐心查找资料,了解各版本间的编译或使用差异。复制错误信息上网搜索解决方案,逐步解决问题,最终成功引入库。
总结经验:**耐心查找资料**,理解库版本间的区别,遇到错误时直接查找解决方案,分步操作以确保成功。
引用资源:
1. **AndroidStudio使用OpenCV的三种方式**
2. **在macOS上使用 NDK20 编译ffmpeg4.2.2的过程** 作者:koinzhang 链接:juejin.cn/post/69513309...
‘肆’ 如何在Android中使用OpenCV
如何在Android程序中使用OpenCV
有两种方式(重点讲后面一种):
1.使用OpenCV java API。
OpenCV安装路径"F:OpenCV-2.3.1-android-bin"下有两个文件夹,
将文件夹"OpenCV-2.3.1"拷贝到你的Eclipse工作空间所在的目录,也就是在你的项目的上一级目录中,然后导入到工作空间中,在Package Explorer中选择你的项目,单机右键在弹出菜单中选择Properties,然后在弹出的Properties窗口中左侧选择Android,然后点击右下方的Add按钮,选择OpenCV-2.3.1并点击OK,
此时,展开你的项目树,你可以看到新加了一个OpenCV-2.3.1_src目录,如下图,那么就是正确添加了OpenCV Java API,否则就是你放置OpenCV-2.3.1的目录路径不正确。
然后就可以在你的Java源文件中导入OpenCV的API包,并且使用OpenCV API了,OpenCV API的包的形式如下:
Org.opencv.(OpenCV模块名).(OpenCV类名)
例如:
Org.opencv.core.Mat
2.利用JNI编写C++ OpenCV代码,通过Android NDK创建动态库(.so)
新建一个工作空间,例如"TestOpenCV",在Window->Preferences中设置好Android SDK的路径。
然后新建一个Android项目,Build Target选择Android2.2,命名为"HaveImgFun",活动名改为HaveImgFun,Package name中填写com.testopencv.haveimgfun,最后点击finish。
如同使用OpenCV Java API那样,将OpenCV-2.3.1文件夹拷贝到与工作空间同一级目录中;另外,将"F:OpenCV-2.3.1-android-binsamples"下的includeOpenCV.mk文件拷贝到和项目HaveImgFun同一级目录中:
(上面这个各个文件夹和文件的放置很重要,因为OpenCV-2.3.1下的OpenCV.mk中有很多相对路径的指定,如果不是这样放置,在NDK生成动态库时可能会报文件或文件夹无法找到的错误)
选择Package Explorer中你的项目,右键选择new->folder,新建一个名为jni的文件夹,用来存放你的c/c++代码。
然后把res->layout下的main.xml的内容改为下面所示:
1 <?xml version="1.0" encoding="utf-8"?>
2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:orientation="vertical"
4 android:layout_width="fill_parent"
5 android:layout_height="fill_parent"
6 >
7 <Button android:layout_height="wrap_content"
8 android:layout_width="fill_parent"
9 android:id="@+id/btnNDK"
10 android:text="使用C++ OpenCV进行处理" />
11 <Button android:layout_height="wrap_content"
12 android:layout_width="fill_parent"
13 android:id="@+id/btnRestore"
14 android:text="还原" />
15 <ImageView android:id="@+id/ImageView01"
16 android:layout_width="fill_parent"
17 android:layout_height="fill_parent" />
18 </LinearLayout>
上面的代码就是一个线性布局里面包含2个按钮加上一个显示图像的ImageView
在文件夹src下的com.testopencv.haveimgfun包中新建一个类用于包装使用了opencv c++代码的动态库的导出函数,类名为LibImgFun。
Eclipse会为你创建一个新的文件LibImgFun.java,将里面的内容改为:
1 package com.testopencv.haveimgfun;
2 public class LibImgFun {
3 static {
4 System.loadLibrary("ImgFun");
5 }
6 /**
7 * @param width the current view width
8 * @param height the current view height
9 */
10 public static native int[] ImgFun(int[] buf, int w, int h);
11 }
从上面的代码可以得知,我们的动态库名字应该为“libImgFun.so”,注意"public static native int[] ImgFun(int[] buf, int w, int h)"中的native关键字,表明这个函数来自native code。static表示这是一个静态函数,这样就可以直接用类名去调用。
在jni文件夹下建立一个"ImgFun.cpp"的文件,内容改为下面所示:
1 #include <jni.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <opencv2/opencv.hpp>
5 using namespace cv;
6
7 extern "C"
8 {
9 JNIEXPORT jintArray JNICALL Java_com_testopencv_haveimgfun_LibImgFun_ImgFun(JNIEnv* env, jobject obj, jintArray buf, int w, int h);
10 JNIEXPORT jintArray JNICALL Java_com_testopencv_haveimgfun_LibImgFun_ImgFun(JNIEnv* env, jobject obj, jintArray buf, int w, int h){
11
12 jint *cbuf;
13 cbuf = env->GetIntArrayElements(buf, false);
14 if(cbuf == NULL)
15 {
16 return 0;
17 }
18
19 Mat myimg(h, w, CV_8UC4, (unsigned char*)cbuf);
20 for(int j=0;j<myimg.rows/2;j++)
21 {
22 myimg.row(j).setTo(Scalar(0,0,0,0));
23 }
24
25 int size=w * h;
26 jintArray result = env->NewIntArray(size);
27 env->SetIntArrayRegion(result, 0, size, cbuf);
28 env->ReleaseIntArrayElements(buf, cbuf, 0);
29 return result;
30 }
31 }
上面的代码中#include <jni.h>是必须要包含的头文件,#include <opencv2/opencv.hpp>是opencv要包含的头文件。
动态库要导出的函数如下声明:
JNIEXPORT jintArray JNICALL Java_com_testopencv_haveimgfun_LibImgFun_ImgFun(JNIEnv* env, jobject obj, jintArray buf, int w, int h);
JNIEXPORT 和JNICALL是必须要加的关键字
jintArray就是int[],这里返回类型要么为空,要么为jni中定义的类型,事实上就是CC++类型前面加上j,如果是数组,则在后面加上Array。
函数名的命名规则如下:
Java_(包路径)_(类名)_(函数名) (JNIEnv *env, jobject obj, 自己定义的参数...)
包路径中的"."用"_"(下划线)代替,类名就是上面包装该动态库函数的类的名字,最后一个才是真正的函数名;JNIEnv *env和jobject obj这两个参数时必须的,用来调用JNI环境下的一些函数;后面就是你自己定义的参数。在这里,jintArray buf代表了传进来的图像的数据,int w是图像的宽,int h是图像的高。
这个函数的功能是将传进来的图像的上半部分涂成黑色。
然后再在jni下新建两个文件"Android.mk"文件和"Application.mk"文件,这两个文件事实上就是简单的Makefile文件。
其中将Android.mk的内容改为如下所示:
LOCAL_PATH:=$(callmy-dir)
include$(CLEAR_VARS)
include../includeOpenCV.mk
ifeq("$(wildcard$(OPENCV_MK_PATH))","")
#trytoloadOpenCV.mkfromdefaultinstalllocation
include$(TOOLCHAIN_PREBUILT_ROOT)/user/share/OpenCV/OpenCV.mk
else
include$(OPENCV_MK_PATH)
endif
LOCAL_MODULE:=ImgFun
LOCAL_SRC_FILES:=ImgFun.cpp
include$(BUILD_SHARED_LIBRARY)
Application.mk的内容改为如下所示:
APP_STL:=gnustl_static
APP_CPPFLAGS:=-frtti-fexceptions
APP_ABI:=armeabiarmeabi-v7a
其中APP_ABI指定的是目标平台的CPU架构。(经过很多测试,android2.2必须指定为armeabi,android2.2以上的使用armeabi-v7a,如果没有设置对,很有可能安装到android虚拟机失败,当然你同时如上面写上也是可以的)
上面的步骤完成后,就可以使用NDK生成动态库了,打开cygwin,cd到项目目录下:
输入$NDK/ndk-build命令,开始创建动态库。
这时候刷新Eclipse的Package Explorer会出现两个新的文件夹obj和libs。
现在,只剩最后一步完成这个测试程序。
将一张图片,例如"lena.jpg"放到项目res->drawable-hdpi目录中并刷新该目录。
然后将HaveImgFun.java的内容改为下面所示:
1 package com.testopencv.haveimgfun;
2
3 import android.app.Activity;
4 import android.graphics.Bitmap;
5 import android.graphics.Bitmap.Config;
6 import android.graphics.drawable.BitmapDrawable;
7 import android.os.Bundle;
8 import android.widget.Button;
9 import android.view.View;
10 import android.widget.ImageView;
11
12 public class HaveImgFun extends Activity{
13 /** Called when the activity is first created. */
14 ImageView imgView;
15 Button btnNDK, btnRestore;
16
17 @Override
18 public void onCreate(Bundle savedInstanceState) {
19 super.onCreate(savedInstanceState);
20 setContentView(R.layout.main);
21
22 this.setTitle("使用NDK转换灰度图");
23 btnRestore=(Button)this.findViewById(R.id.btnRestore);
24 btnRestore.setOnClickListener(new ClickEvent());
25 btnNDK=(Button)this.findViewById(R.id.btnNDK);
26 btnNDK.setOnClickListener(new ClickEvent());
27 imgView=(ImageView)this.findViewById(R.id.ImageView01);
28 Bitmap img=((BitmapDrawable) getResources().getDrawable(R.drawable.lena)).getBitmap();
29 imgView.setImageBitmap(img);
30 }
31
32 class ClickEvent implements View.OnClickListener{
33 public void onClick(View v){
34 if(v == btnNDK){
35 long current=System.currentTimeMillis();
36 Bitmap img1=((BitmapDrawable) getResources().getDrawable(R.drawable.lena)).getBitmap();
37 int w=img1.getWidth(),h=img1.getHeight();
38 int[] pix = new int[w * h];
39 img1.getPixels(pix, 0, w, 0, 0, w, h);
40 int[] resultInt=LibImgFun.ImgFun(pix, w, h);
41 Bitmap resultImg=Bitmap.createBitmap(w, h, Config.RGB_565);
42 resultImg.setPixels(resultInt, 0, w, 0, 0,w, h);
43 long performance=System.currentTimeMillis()-current;
44 imgView.setImageBitmap(resultImg);
45 HaveImgFun.this.setTitle("w:"+String.valueOf(img1.getWidth())+",h:"+String.valueOf(img1.getHeight())+"NDK耗时"+String.valueOf(performance)+" 毫秒");
46 } else if(v == btnRestore){
47 Bitmap img2=((BitmapDrawable) getResources().getDrawable(R.drawable.lena)).getBitmap();
48 imgView.setImageBitmap(img2);
49 HaveImgFun.this.setTitle("使用OpenCV进行图像处理");
50 }
51 }
52 }
53 }
点击全部保存,OK,现在可以选择一个Android虚拟机运行看一下效果,配置好Run Configuration然后点击Run,得到下面的结果: