forked from bytedeco/javacv
-
Notifications
You must be signed in to change notification settings - Fork 0
/
FacePreview.java
290 lines (253 loc) · 10.5 KB
/
FacePreview.java
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
/*
* Copyright (C) 2010-2019 Samuel Audet
*
* FacePreview - A fusion of OpenCV's facedetect and Android's CameraPreview samples,
* with JavaCV + JavaCPP as the glue in between.
*
* This file was based on CameraPreview.java that came with the Samples for
* Android SDK API 8, revision 1 and contained the following copyright notice:
*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
* IMPORTANT - Make sure the AndroidManifest.xml file looks like this:
*
* <?xml version="1.0" encoding="utf-8"?>
* <manifest xmlns:android="http://schemas.android.com/apk/res/android"
* package="org.bytedeco.javacv.facepreview"
* android:versionCode="1"
* android:versionName="1.0" >
* <uses-sdk android:minSdkVersion="4" />
* <uses-permission android:name="android.permission.CAMERA" />
* <uses-feature android:name="android.hardware.camera" />
* <application android:label="@string/app_name">
* <activity
* android:name="FacePreview"
* android:label="@string/app_name"
* android:screenOrientation="landscape">
* <intent-filter>
* <action android:name="android.intent.action.MAIN" />
* <category android:name="android.intent.category.LAUNCHER" />
* </intent-filter>
* </activity>
* </application>
* </manifest>
*/
package org.bytedeco.javacv.facepreview;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ImageFormat;
import android.graphics.Paint;
import android.hardware.Camera;
import android.hardware.Camera.Size;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.List;
import org.bytedeco.javacpp.Loader;
import org.bytedeco.opencv.opencv_core.*;
import org.bytedeco.opencv.opencv_objdetect.*;
import static org.bytedeco.opencv.global.opencv_core.*;
import static org.bytedeco.opencv.global.opencv_objdetect.*;
// ----------------------------------------------------------------------
public class FacePreview extends Activity {
private FrameLayout layout;
private FaceView faceView;
private Preview mPreview;
@Override
protected void onCreate(Bundle savedInstanceState) {
// Hide the window title.
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
// Create our Preview view and set it as the content of our activity.
try {
layout = new FrameLayout(this);
faceView = new FaceView(this);
mPreview = new Preview(this, faceView);
layout.addView(mPreview);
layout.addView(faceView);
setContentView(layout);
} catch (IOException e) {
e.printStackTrace();
new AlertDialog.Builder(this).setMessage(e.getMessage()).create().show();
}
}
}
// ----------------------------------------------------------------------
class FaceView extends View implements Camera.PreviewCallback {
public static final int SUBSAMPLING_FACTOR = 4;
private Mat grayImage;
private CascadeClassifier classifier;
private RectVector faces;
public FaceView(FacePreview context) throws IOException {
super(context);
// Load the classifier file from Java resources.
File classifierFile = Loader.extractResource(getClass(),
"/org/bytedeco/javacv/facepreview/haarcascade_frontalface_alt.xml",
context.getCacheDir(), "classifier", ".xml");
if (classifierFile == null || classifierFile.length() <= 0) {
throw new IOException("Could not extract the classifier file from Java resource.");
}
classifier = new CascadeClassifier(classifierFile.getAbsolutePath());
classifierFile.delete();
if (classifier.isNull()) {
throw new IOException("Could not load the classifier file.");
}
}
public void onPreviewFrame(final byte[] data, final Camera camera) {
try {
Camera.Size size = camera.getParameters().getPreviewSize();
processImage(data, size.width, size.height);
camera.addCallbackBuffer(data);
} catch (RuntimeException e) {
// The camera has probably just been released, ignore.
}
}
protected void processImage(byte[] data, int width, int height) {
// First, downsample our image and convert it into a grayscale Mat
int f = SUBSAMPLING_FACTOR;
if (grayImage == null || grayImage.cols() != width/f || grayImage.rows() != height/f) {
grayImage = new Mat(height/f, width/f, CV_8UC1);
}
int imageWidth = grayImage.cols();
int imageHeight = grayImage.rows();
int dataStride = f*width;
int imageStride = (int)grayImage.step(0);
ByteBuffer imageBuffer = grayImage.createBuffer();
for (int y = 0; y < imageHeight; y++) {
int dataLine = y*dataStride;
int imageLine = y*imageStride;
for (int x = 0; x < imageWidth; x++) {
imageBuffer.put(imageLine + x, data[dataLine + f*x]);
}
}
faces = new RectVector();
classifier.detectMultiScale(grayImage, faces);
postInvalidate();
}
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setTextSize(20);
String s = "FacePreview - This side up.";
float textWidth = paint.measureText(s);
canvas.drawText(s, (getWidth()-textWidth)/2, 20, paint);
if (faces != null) {
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.STROKE);
float scaleX = (float)getWidth()/grayImage.cols();
float scaleY = (float)getHeight()/grayImage.rows();
long total = faces.size();
for (long i = 0; i < total; i++) {
Rect r = faces.get(i);
int x = r.x(), y = r.y(), w = r.width(), h = r.height();
canvas.drawRect(x*scaleX, y*scaleY, (x+w)*scaleX, (y+h)*scaleY, paint);
}
}
}
}
// ----------------------------------------------------------------------
class Preview extends SurfaceView implements SurfaceHolder.Callback {
SurfaceHolder mHolder;
Camera mCamera;
Camera.PreviewCallback previewCallback;
Preview(Context context, Camera.PreviewCallback previewCallback) {
super(context);
this.previewCallback = previewCallback;
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void surfaceCreated(SurfaceHolder holder) {
// The Surface has been created, acquire the camera and tell it where
// to draw.
mCamera = Camera.open();
try {
mCamera.setPreviewDisplay(holder);
} catch (IOException exception) {
mCamera.release();
mCamera = null;
// TODO: add more exception handling logic here
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
// Surface will be destroyed when we return, so stop the preview.
// Because the CameraDevice object is not a shared resource, it's very
// important to release it when the activity is paused.
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.05;
double targetRatio = (double) w / h;
if (sizes == null) return null;
Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
// Try to find an size match aspect ratio and size
for (Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
// Cannot find the one match the aspect ratio, ignore the requirement
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
// Now that the size is known, set up the camera parameters and begin
// the preview.
Camera.Parameters parameters = mCamera.getParameters();
List<Size> sizes = parameters.getSupportedPreviewSizes();
Size optimalSize = getOptimalPreviewSize(sizes, w, h);
parameters.setPreviewSize(optimalSize.width, optimalSize.height);
mCamera.setParameters(parameters);
if (previewCallback != null) {
mCamera.setPreviewCallbackWithBuffer(previewCallback);
Camera.Size size = parameters.getPreviewSize();
byte[] data = new byte[size.width*size.height*
ImageFormat.getBitsPerPixel(parameters.getPreviewFormat())/8];
mCamera.addCallbackBuffer(data);
}
mCamera.startPreview();
}
}