充电站项目mavic pro自动控制app源码
在上一篇文章中写了怎样训练出cascade.xml文件,这一篇讲解怎样在android app里使用级联分类器进行多目标检测。

首先要做的是在app中导入opencv4android的SDK。opencv4android SDK

配置环境:OpenCV On Android环境配置指南

本项目采用动态链接库,免去安装额外的OpenCV Manager App。

图像处理与识别部分在src的application下的PictureHandle里。

使用步骤:

1.把cascade.xml文件放入res下的raw文件夹里。

2.在activity里加载opencv库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

@Override

protected void onResume() {

super.onResume();

//load OpenCV engine and init OpenCV library

if(!OpenCVLoader.initDebug()) {

OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION, getApplicationContext(), mLoaderCallback);

Log.d(TAG, "Internal OpenCV library not found. Using Opencv Manager for initialization");

}else {

Log.d(TAG, "OpenCV library found inside package. Using it!");

mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);

}

}

3.设置链接到opencv库的回调函数mLoaderCallback。在该回调函数里,我们加载cascade.xml文件,并对CascadeClassifier做初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS:
isInit = true;
try {
// load cascade file from application resources
InputStream is = context.getResources().openRawResource(R.raw.cascade1);
File cascade1Dir = context.getDir("cascade1", Context.MODE_PRIVATE);
mCascade1File = new File(cascade1Dir, "cascade1.xml");
FileOutputStream os = new FileOutputStream(mCascade1File);

byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
is.close();
os.close();

// load cascade file from application resources
InputStream ise = context.getResources().openRawResource(R.raw.cascade2);
File cascade2Dir = context.getDir("cascade2", Context.MODE_PRIVATE);
mCascade2File = new File(cascade2Dir, "cascade2.xml");
FileOutputStream ose = new FileOutputStream(mCascade2File);

while ((bytesRead = ise.read(buffer)) != -1) {
ose.write(buffer, 0, bytesRead);
}

ise.close();
ose.close();

mJavaDetector1 = new CascadeClassifier(mCascade1File.getAbsolutePath());
if (mJavaDetector1.empty()) {
Log.e(TAG, "Failed to load cascade classifier");
mJavaDetector1 = null;
} else
Log.i(TAG, "Loaded cascade classifier from " + mCascade1File.getAbsolutePath());

mJavaDetector2 = new CascadeClassifier(mCascade2File.getAbsolutePath());
if (mJavaDetector2.empty()) {
Log.e(TAG, "Failed to load cascade classifier");
mJavaDetector2 = null;
} else {
Log.i(TAG, "Loaded cascade classifier from " + mCascade2File.getAbsolutePath());
}

cascade1Dir.delete();
cascade2Dir.delete();
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "Failed to load cascade. Exception thrown: " + e);
}
break;
default:
isInit = false;
super.onManagerConnected(status);
break;
}
}

4.新建的Picture_Detector1类。mavic pro的摄像头传回到app上的预览画面使用安卓texture,我们先从texture里取出一帧图片,保存到bitmap类的变量里。然后将bitmap转为opencv的mat类型数据,接着将图片转为灰度图像,传入detectMultiScale方法里检测目标。
下面是新建的Picture_Detector1类,开一个线程处理图像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Picture_Detector1 implements Runnable{
private Bitmap bitmap;
private Handler UIHandler;
private Rect[] result;

public void begin() {
new Thread(this).start();
}
public Picture_Detector1(Bitmap bitmap,Handler UIHandler){
this.bitmap = bitmap;
this.UIHandler = UIHandler;
}

@Override
public void run() {
Mat srcimg = new Mat();
Utils.bitmapToMat(bitmap, srcimg);
Imgproc.cvtColor(srcimg, srcimg,Imgproc.COLOR_RGBA2GRAY);
result = detector1(srcimg);
Message msg = new Message();
Bundle b = new Bundle();// 存放数据
b.putSerializable("picturedetector1", result);
msg.setData(b);
UIHandler.sendMessage(msg);
}
}

图像处理函数为 result = detector1(srcimg);
函数代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public Rect[] detector1(Mat srcimg) {
if (mAbsoluteTargetSize == 0) {
int height = srcimg.rows();
if (Math.round(height * mRelativeTargetSize) > 0) {
mAbsoluteTargetSize = Math.round(height * mRelativeTargetSize);
}
}
MatOfRect targets = new MatOfRect();
if (mDetectorType == JAVA_DETECTOR) {
if (mJavaDetector1 != null)
mJavaDetector1.detectMultiScale(srcimg, targets, 1.1, 6, 2, // TODO: objdetect.CV_HAAR_SCALE_IMAGE
new Size(mAbsoluteTargetSize, mAbsoluteTargetSize), new Size());
}
else {
Log.e(TAG, "Detection method is not selected!");
}
return targets.toArray();
}

CascadeClassifier为级联分类器检测类,使用Adaboost的方法,提取LBP\HOG\HAAR特征进行目标检测,加载traincascade进行训练的分类器。
detectMultiscale函数为多尺度多目标检测:
detectMultiScale能够实现多尺度检测,但多尺度检测是通过缩放图像来完成的。多尺度:通常搜索目标的模板尺寸大小是固定的,但是不同图片大小不同,所以目标对象的大小也是不定的,所以多尺度即不断缩放图片大小(缩放到与模板匹配),通过模板滑动窗函数搜索匹配;同一副图片可能在不同尺度下都得到匹配值,所以多尺度检测函数detectMultiscale是多尺度合并的结果。
多目标:通过检测符合模板匹配对象,可得到多个目标,均输出到objects向量里面。
detectMultiScale函数参数解释:
const Mat, —Mat类型的图像,待检测图片,一般为灰度图像加快检测速度。
Rect[],—检测得到的被检测物体的矩形框向量组;为输出量
doublescaleFactor, —图像缩放因子,必须大于1,表示在前后两次相继的扫描中,搜索窗口的比例系数。默认为1.1即每次搜索窗口依次扩大10%;一般设置为1.1 。
intminNeighbors,—构成检测目标的相邻矩形的最小个数(默认为3个)。如果组成检测目标的小矩形的个数和小于 min_neighbors - 1 都会被排除。
如果min_neighbors 为 0, 则函数不做任何操作就返回所有的被检候选矩形框,
这种设定值一般用在用户自定义对检测结果的组合程序上。
int flags, —要么使用默认值,要么使用CV_HAAR_DO_CANNY_PRUNING,如果设置为CV_HAAR_DO_CANNY_PRUNING,那么函数将会使用Canny边缘检测来排除边缘过多或过少的区域。
Size minObjectSize, —最小检测窗口大小
Size maxObjectSize)—最大检测窗口大小(默认是图像大小)
5.在activity里调用Picture_Detector:
新建myhandler,用于处理从线程中接收的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
private Rect[] targets1Array = null;
private Rect[] targets2Array = null;
private MyHandler1 myHandler1;
private MyHandler2 myHandler2;
@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_camera);
super.onCreate(savedInstanceState);
initUI();
myHandler1 = new MyHandler1();
myHandler2 = new MyHandler2();
}


class MyHandler1 extends Handler {
public MyHandler1() {
}

public MyHandler1(Looper L) {
super(L);
}
// 子类必须重写此方法,接受数据
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
// 此处可以更新UI
Bundle b = msg.getData();
targets1Array = (Rect[]) b.getSerializable("picturedetector1");
if(targets1Array.length > 0){
CameraActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
mTrackingImage1.setX(targets1Array[0].x);
mTrackingImage1.setY(targets1Array[0].y);
mTrackingImage1.getLayoutParams().width = targets1Array[0].width;
mTrackingImage1.getLayoutParams().height = targets1Array[0].height;
mTrackingImage1.requestLayout();
mTrackingImage1.setVisibility(View.VISIBLE);
}
});
}else {
CameraActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
mTrackingImage1.setVisibility(View.INVISIBLE);
}
});
}
}
}

重写onSurfaceTextureUpdated回调函数,用于实时图像处理

1
2
3
4
5
6
7
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
super.onSurfaceTextureUpdated(surface);
Bitmap bitmap = mVideoSurface.getBitmap();
picturehandle.new Picture_Detector1(bitmap,myHandler1).begin();
picturehandle.new Picture_Detector2(bitmap,myHandler2).begin();
}