Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Detect Faces in Video
  • Loading branch information
dragosholban committed Apr 6, 2018
1 parent a85a4d5 commit c473fcb
Show file tree
Hide file tree
Showing 10 changed files with 602 additions and 1 deletion.
Binary file modified .idea/caches/build_file_checksums.ser
Binary file not shown.
4 changes: 3 additions & 1 deletion app/src/main/AndroidManifest.xml
Expand Up @@ -3,6 +3,7 @@
package="com.dragosholban.androidfacedetection">

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />

<application
android:allowBackup="true"
Expand Down Expand Up @@ -33,7 +34,8 @@
android:resource="@xml/file_paths" />
</provider>

<activity android:name=".FaceDetectionActivity"></activity>
<activity android:name=".FaceDetectionActivity" />
<activity android:name=".VideoFaceDetectionActivity"></activity>
</application>

</manifest>
@@ -0,0 +1,167 @@
package com.dragosholban.androidfacedetection;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup;

import com.google.android.gms.common.images.Size;
import com.google.android.gms.vision.CameraSource;

import java.io.IOException;

public class CameraPreview extends ViewGroup {
private static final String TAG = "CameraPreview";

private Context mContext;
private SurfaceView mSurfaceView;
private boolean mStartRequested;
private boolean mSurfaceAvailable;
private CameraSource mCameraSource;

private GraphicOverlay mOverlay;

public CameraPreview(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mStartRequested = false;
mSurfaceAvailable = false;

mSurfaceView = new SurfaceView(context);
mSurfaceView.getHolder().addCallback(new SurfaceCallback());
addView(mSurfaceView);
}

public void start(CameraSource cameraSource) throws IOException {
if (cameraSource == null) {
stop();
}

mCameraSource = cameraSource;

if (mCameraSource != null) {
mStartRequested = true;
startIfReady();
}
}

public void start(CameraSource cameraSource, GraphicOverlay overlay) throws IOException {
mOverlay = overlay;
start(cameraSource);
}

public void stop() {
if (mCameraSource != null) {
mCameraSource.stop();
}
}

public void release() {
if (mCameraSource != null) {
mCameraSource.release();
mCameraSource = null;
}
}

@SuppressLint("MissingPermission")
private void startIfReady() throws IOException {
if (mStartRequested && mSurfaceAvailable) {
mCameraSource.start(mSurfaceView.getHolder());
if (mOverlay != null) {
Size size = mCameraSource.getPreviewSize();
int min = Math.min(size.getWidth(), size.getHeight());
int max = Math.max(size.getWidth(), size.getHeight());
if (isPortraitMode()) {
// Swap width and height sizes when in portrait, since it will be rotated by
// 90 degrees
mOverlay.setCameraInfo(min, max, mCameraSource.getCameraFacing());
} else {
mOverlay.setCameraInfo(max, min, mCameraSource.getCameraFacing());
}
mOverlay.clear();
}
mStartRequested = false;
}
}

private class SurfaceCallback implements SurfaceHolder.Callback {
@Override
public void surfaceCreated(SurfaceHolder surface) {
mSurfaceAvailable = true;
try {
startIfReady();
} catch (IOException e) {
Log.e(TAG, "Could not start camera source.", e);
}
}

@Override
public void surfaceDestroyed(SurfaceHolder surface) {
mSurfaceAvailable = false;
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int width = 320;
int height = 240;
if (mCameraSource != null) {
Size size = mCameraSource.getPreviewSize();
if (size != null) {
width = size.getWidth();
height = size.getHeight();
}
}

// Swap width and height sizes when in portrait, since it will be rotated 90 degrees
if (isPortraitMode()) {
int tmp = width;
width = height;
height = tmp;
}

final int layoutWidth = right - left;
final int layoutHeight = bottom - top;

// Computes height and width for potentially doing fit width.
int childWidth = layoutWidth;
int childHeight = (int)(((float) layoutWidth / (float) width) * height);

// If height is too tall using fit width, does fit height instead.
if (childHeight > layoutHeight) {
childHeight = layoutHeight;
childWidth = (int)(((float) layoutHeight / (float) height) * width);
}

for (int i = 0; i < getChildCount(); ++i) {
getChildAt(i).layout(0, 0, childWidth, childHeight);
}

try {
startIfReady();
} catch (IOException e) {
Log.e(TAG, "Could not start camera source.", e);
}
}

private boolean isPortraitMode() {
int orientation = mContext.getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
return false;
}
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
return true;
}

Log.d(TAG, "isPortraitMode returning false by default");
return false;
}
}
@@ -0,0 +1,101 @@
package com.dragosholban.androidfacedetection;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;

import com.google.android.gms.vision.face.Face;
import com.google.android.gms.vision.face.Landmark;

class FaceGraphic extends GraphicOverlay.Graphic {
private static final float FACE_POSITION_RADIUS = 10.0f;
private static final float ID_TEXT_SIZE = 40.0f;
private static final float ID_Y_OFFSET = 50.0f;
private static final float ID_X_OFFSET = -50.0f;
private static final float BOX_STROKE_WIDTH = 5.0f;

private static final int COLOR_CHOICES[] = {
Color.BLUE,
Color.CYAN,
Color.GREEN,
Color.MAGENTA,
Color.RED,
Color.WHITE,
Color.YELLOW
};
private static int mCurrentColorIndex = 0;

private Paint mFacePositionPaint;
private Paint mIdPaint;
private Paint mBoxPaint;

private volatile Face mFace;
private int mFaceId;

FaceGraphic(GraphicOverlay overlay) {
super(overlay);

mCurrentColorIndex = (mCurrentColorIndex + 1) % COLOR_CHOICES.length;
final int selectedColor = COLOR_CHOICES[mCurrentColorIndex];

mFacePositionPaint = new Paint();
mFacePositionPaint.setColor(selectedColor);

mIdPaint = new Paint();
mIdPaint.setColor(selectedColor);
mIdPaint.setTextSize(ID_TEXT_SIZE);

mBoxPaint = new Paint();
mBoxPaint.setColor(selectedColor);
mBoxPaint.setStyle(Paint.Style.STROKE);
mBoxPaint.setStrokeWidth(BOX_STROKE_WIDTH);
}

void setId(int id) {
mFaceId = id;
}


/**
* Updates the face instance from the detection of the most recent frame. Invalidates the
* relevant portions of the overlay to trigger a redraw.
*/
void updateFace(Face face) {
mFace = face;
postInvalidate();
}

/**
* Draws the face annotations for position on the supplied canvas.
*/
@Override
public void draw(Canvas canvas) {
Face face = mFace;
if (face == null) {
return;
}

// Draws a circle at the position of the detected face, with the face's track id below.
float x = translateX(face.getPosition().x + face.getWidth() / 2);
float y = translateY(face.getPosition().y + face.getHeight() / 2);
canvas.drawCircle(x, y, FACE_POSITION_RADIUS, mFacePositionPaint);
canvas.drawText("id: " + mFaceId, x + ID_X_OFFSET, y + ID_Y_OFFSET, mIdPaint);

// Draws a bounding box around the face.
float xOffset = scaleX(face.getWidth() / 2.0f);
float yOffset = scaleY(face.getHeight() / 2.0f);
float left = x - xOffset;
float top = y - yOffset;
float right = x + xOffset;
float bottom = y + yOffset;
canvas.drawRect(left, top, right, bottom, mBoxPaint);

// Draws a circle for each face feature detected
for (Landmark landmark : face.getLandmarks()) {
// the preview display of front-facing cameras is flipped horizontally
float cx = canvas.getWidth() - scaleX(landmark.getPosition().x);
float cy = scaleY(landmark.getPosition().y);
canvas.drawCircle(cx, cy, 10, mIdPaint);
}
}
}
@@ -0,0 +1,51 @@
package com.dragosholban.androidfacedetection;

import com.google.android.gms.vision.Tracker;
import com.google.android.gms.vision.face.Face;
import com.google.android.gms.vision.face.FaceDetector;

class GraphicFaceTracker extends Tracker<Face> {
private GraphicOverlay mOverlay;
private FaceGraphic mFaceGraphic;

GraphicFaceTracker(GraphicOverlay overlay) {
mOverlay = overlay;
mFaceGraphic = new FaceGraphic(overlay);
}

/**
* Start tracking the detected face instance within the face overlay.
*/
@Override
public void onNewItem(int faceId, Face item) {
mFaceGraphic.setId(faceId);
}

/**
* Update the position/characteristics of the face within the overlay.
*/
@Override
public void onUpdate(FaceDetector.Detections<Face> detectionResults, Face face) {
mOverlay.add(mFaceGraphic);
mFaceGraphic.updateFace(face);
}

/**
* Hide the graphic when the corresponding face was not detected. This can happen for
* intermediate frames temporarily (e.g., if the face was momentarily blocked from
* view).
*/
@Override
public void onMissing(FaceDetector.Detections<Face> detectionResults) {
mOverlay.remove(mFaceGraphic);
}

/**
* Called when the face is assumed to be gone for good. Remove the graphic annotation from
* the overlay.
*/
@Override
public void onDone() {
mOverlay.remove(mFaceGraphic);
}
}

0 comments on commit c473fcb

Please sign in to comment.