Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit f8e0ebe

Browse files
created "googly eyes" demo app
This is the code for an app that I will demonstrate at Google I/O. It tracks eye landmarks and state in real time, overlaying a googly eyes animation over the eyes as fast as possible. It can be used in either front facing or rear facing modes. Each mode is optimized differently -- front facing tracks a single large face (faster) and rear facing mode will track multiple, smaller faces (slower). Change-Id: I703fdcf620ed5998227c962238db76c30f63ffd7
1 parent 25a39c5 commit f8e0ebe

File tree

21 files changed

+1772
-0
lines changed

21 files changed

+1772
-0
lines changed

‎visionSamples/googly-eyes/.gitignore‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.gradle
2+
/local.properties
3+
/.idea/workspace.xml
4+
/.idea/libraries
5+
.DS_Store
6+
/build
7+
/captures
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
apply plugin: 'com.android.application'
2+
3+
android {
4+
compileSdkVersion 23
5+
buildToolsVersion "23.0.2"
6+
7+
defaultConfig {
8+
applicationId "com.google.android.gms.samples.vision.face.googlyeyes"
9+
minSdkVersion 9
10+
targetSdkVersion 23
11+
versionCode 1
12+
versionName "1.0"
13+
}
14+
buildTypes {
15+
release {
16+
minifyEnabled false
17+
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18+
}
19+
}
20+
}
21+
22+
dependencies {
23+
compile fileTree(dir: 'libs', include: ['*.jar'])
24+
compile 'com.android.support:support-v4:23+'
25+
compile 'com.google.android.gms:play-services:8.4.0'
26+
compile 'com.android.support:design:23.0.0'
27+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Add project specific ProGuard rules here.
2+
# By default, the flags in this file are appended to flags specified
3+
# in /usr/local/google/home/wilkinsonclay/android/adt-bundle-linux-x86_64-20140702/sdk/tools/proguard/proguard-android.txt
4+
# You can edit the include path and order by changing the proguardFiles
5+
# directive in build.gradle.
6+
#
7+
# For more details, see
8+
# http://developer.android.com/guide/developing/tools/proguard.html
9+
10+
# Add any project specific keep options here:
11+
12+
# If your project uses WebView with JS, uncomment the following
13+
# and specify the fully qualified class name to the JavaScript interface
14+
# class:
15+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16+
# public *;
17+
#}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
package="com.google.android.gms.samples.vision.face.googlyeyes"
4+
android:installLocation="auto"
5+
android:versionCode="1"
6+
android:versionName="1">
7+
8+
<uses-feature android:name="android.hardware.camera" />
9+
10+
<uses-permission android:name="android.permission.CAMERA" />
11+
12+
<application
13+
android:allowBackup="true"
14+
android:hardwareAccelerated="true"
15+
android:icon="@drawable/icon"
16+
android:theme="@style/Theme.AppCompat"
17+
android:label="Googly Eyes">
18+
19+
<meta-data android:name="com.google.android.gms.version"
20+
android:value="@integer/google_play_services_version"/>
21+
22+
<meta-data
23+
android:name="com.google.android.gms.vision.DEPENDENCIES"
24+
android:value="face" />
25+
26+
<activity
27+
android:name="com.google.android.gms.samples.vision.face.googlyeyes.GooglyEyesActivity"
28+
android:icon="@drawable/icon"
29+
android:label="Googly Eyes"
30+
android:theme="@style/Theme.AppCompat.NoActionBar"
31+
android:screenOrientation="fullSensor">
32+
<intent-filter>
33+
<action android:name="android.intent.action.MAIN" />
34+
35+
<category android:name="android.intent.category.LAUNCHER" />
36+
</intent-filter>
37+
</activity>
38+
</application>
39+
40+
</manifest>
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
/*
2+
* Copyright (C) The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.android.gms.samples.vision.face.googlyeyes;
17+
18+
import android.graphics.PointF;
19+
import android.os.SystemClock;
20+
21+
/**
22+
* Simulates the physics of motion for an iris which moves within a googly eye. The iris moves
23+
* independently of the motion of the face/eye, and moves according to the following forces:<p>
24+
*
25+
* <ol>
26+
* <li>Gravity - downward acceleration.</li>
27+
*
28+
* <li>Friction - deceleration; opposing motion</li>
29+
*
30+
* <li>Bounce - acceleration in the opposite direction of motion when the iris hits the side of the
31+
* eye (e.g., due to a jerking motion which suddenly moves the face in frame). Note that this is
32+
* the only way to get the iris to move horizontally, since gravity only accelerates downward.</li>
33+
* </ol>
34+
*
35+
* The simulation is configured to run at a universal real time rate, regardless of the performance
36+
* of the device in which it is run and how frequently updates are received.
37+
*/
38+
class EyePhysics {
39+
// The friction and gravity values below are set relative to a specific time period. This
40+
// allows the simulation to run at the same rate, regardless of whether it is running on a slow
41+
// or fast device or if there are temporary performance variations on the device.
42+
private final long TIME_PERIOD_MS = 1000;
43+
44+
private final float FRICTION = 2.2f;
45+
private final float GRAVITY = 40.0f;
46+
47+
private final float BOUNCE_MULTIPLIER = 20.0f;
48+
49+
// Allow slightly non-zero values to be considered to be zero, to converge to zero more quickly.
50+
private final float ZERO_TOLERANCE = 0.001f;
51+
52+
private long mLastUpdateTimeMs = SystemClock.elapsedRealtime();
53+
54+
private PointF mEyePosition;
55+
private float mEyeRadius;
56+
57+
private PointF mIrisPosition;
58+
private float mIrisRadius;
59+
60+
// Velocity is independent of the final rendering coordinate system, so that we don't have to
61+
// change it as the eye gets bigger or smaller by forward and backward motion. This will be
62+
// scaled up proportional to the eye size when updating position.
63+
private float vx = 0.0f;
64+
private float vy = 0.0f;
65+
66+
// Keep track of bounces that immediately occur consecutively, since this means that the
67+
// iris is bouncing too fast. When this happens, we dampen the velocity to avoid infinite
68+
// bounces.
69+
private int mConsecutiveBounces = 0;
70+
71+
//==============================================================================================
72+
// Methods
73+
//==============================================================================================
74+
75+
/**
76+
* Generate the next position of the iris based on simulated velocity, eye boundaries, gravity,
77+
* friction, and bounce momentum.
78+
*/
79+
PointF nextIrisPosition(PointF eyePosition, float eyeRadius, float irisRadius) {
80+
// Correct the current eye position and size based on recent motion of the face within the
81+
// frame. Keep the current iris position, if available.
82+
mEyePosition = eyePosition;
83+
mEyeRadius = eyeRadius;
84+
if (mIrisPosition == null) {
85+
mIrisPosition = eyePosition;
86+
}
87+
mIrisRadius = irisRadius;
88+
89+
// Keep track of time, so that we can consistently update the simulation proportionally to
90+
// how much time has elapsed. This makes the animation rate device-independent. All of the
91+
// velocity changes below are pro-rated based on this.
92+
long nowMs = SystemClock.elapsedRealtime();
93+
long elapsedTimeMs = nowMs - mLastUpdateTimeMs;
94+
float simulationRate = (float) elapsedTimeMs / TIME_PERIOD_MS;
95+
mLastUpdateTimeMs = nowMs;
96+
97+
if (!isStopped()) {
98+
// Only apply gravity when the iris is not stopped at the bottom of the eye.
99+
vy += GRAVITY * simulationRate;
100+
}
101+
102+
// Apply friction in the opposite direction of motion, so that the iris slows in the absence
103+
// of other head motion.
104+
vx = applyFriction(vx, simulationRate);
105+
vy = applyFriction(vy, simulationRate);
106+
107+
// Update the iris position based on velocity. Since velocity is size-independent, scale by
108+
// the iris radius to get the change in position.
109+
float x = mIrisPosition.x + (vx * mIrisRadius * simulationRate);
110+
float y = mIrisPosition.y + (vy * mIrisRadius * simulationRate);
111+
mIrisPosition = new PointF(x, y);
112+
113+
// Correct the position and velocity of the iris if it has gone out of bounds, guaranteeing
114+
// that the returned result is at a valid position within the eye.
115+
makeIrisInBounds(simulationRate);
116+
117+
return mIrisPosition;
118+
}
119+
120+
/**
121+
* Friction slows velocity in the opposite direction of motion, until zero velocity is reached.
122+
*/
123+
private float applyFriction(float velocity, float simulationRate) {
124+
if (isZero(velocity)) {
125+
velocity = 0.0f;
126+
} else if (velocity > 0) {
127+
velocity = Math.max(0.0f, velocity - (FRICTION * simulationRate));
128+
} else {
129+
velocity = Math.min(0.0f, velocity + (FRICTION * simulationRate));
130+
}
131+
return velocity;
132+
}
133+
134+
/**
135+
* Correct the iris position to be in-bounds within the eye, if it is now out of bounds. Being
136+
* out of bounds could have been due to a sudden movement of the head and/or camera, or the
137+
* result of just bouncing/rolling around.<p>
138+
*
139+
* In addition, modify the velocity to cause a bounce in the opposite direction.
140+
*/
141+
private void makeIrisInBounds(float simulationRate) {
142+
float irisOffsetX = mIrisPosition.x - mEyePosition.x;
143+
float irisOffsetY = mIrisPosition.y - mEyePosition.y;
144+
145+
float maxDistance = mEyeRadius - mIrisRadius;
146+
float distance = (float) Math.sqrt(Math.pow(irisOffsetX, 2) + Math.pow(irisOffsetY, 2));
147+
if (distance <= maxDistance) {
148+
// The iris is in bounds, so no correction is necessary.
149+
mConsecutiveBounces = 0;
150+
return;
151+
}
152+
153+
// Accumulate a consecutive bounce count, in order to dampen the momentum of a quickly
154+
// moving iris. Two or more bounces in a row indicates that the iris is moving so fast that
155+
// it doesn't even travel inside the eye. We progressively slow the velocity using this
156+
// count until this is no longer the case.
157+
mConsecutiveBounces++;
158+
159+
// Move the iris back to where it would have been when it would have contacted the side of
160+
// the eye.
161+
float ratio = maxDistance / distance;
162+
float x = mEyePosition.x + (ratio * irisOffsetX);
163+
float y = mEyePosition.y + (ratio * irisOffsetY);
164+
165+
// Update the velocity direction and magnitude to cause a bounce.
166+
167+
float dx = x - mIrisPosition.x;
168+
vx = applyBounce(vx, dx, simulationRate) / mConsecutiveBounces;
169+
170+
float dy = y - mIrisPosition.y;
171+
vy = applyBounce(vy, dy, simulationRate) / mConsecutiveBounces;
172+
173+
mIrisPosition = new PointF(x, y);
174+
}
175+
176+
/**
177+
* Update velocity in response to bouncing off the sides of the eye (i.e., when iris hits the
178+
* bottom or the eye moves quickly). This is the only way to gain horizontal velocity, since
179+
* there is no other horizontal force.
180+
*/
181+
private float applyBounce(float velocity, float distOutOfBounds, float simulationRate) {
182+
if (isZero(distOutOfBounds)) {
183+
// No bounce needed, since we are still in bounds along this dimension.
184+
return velocity;
185+
}
186+
187+
// Reverse velocity to create a bounce in the opposite direction.
188+
velocity *= -1;
189+
190+
// If distOutOfBounds was large, this indicates that the iris was whacked against the side
191+
// of the eye quickly. Add an additional velocity factor to account for the force gained by
192+
// this quick movement, based upon how much it was out of bounds.
193+
float bounce = BOUNCE_MULTIPLIER * Math.abs(distOutOfBounds / mIrisRadius);
194+
if (velocity > 0) {
195+
velocity += bounce * simulationRate;
196+
} else {
197+
velocity -= bounce * simulationRate;
198+
}
199+
200+
return velocity;
201+
}
202+
203+
/**
204+
* The iris is stopped if it is at the bottom of the eye and its velocity is zero.
205+
*/
206+
private boolean isStopped() {
207+
if (mEyePosition.y >= mIrisPosition.y) {
208+
return false;
209+
}
210+
211+
float irisOffsetY = mIrisPosition.y - mEyePosition.y;
212+
float maxDistance = mEyeRadius - mIrisRadius;
213+
if (irisOffsetY < maxDistance) {
214+
return false;
215+
}
216+
217+
return (isZero(vx) && isZero(vy));
218+
}
219+
220+
/**
221+
* Allow for a small tolerance in floating point values in considering whether a value is zero.
222+
*/
223+
private boolean isZero(float num) {
224+
return ((num < ZERO_TOLERANCE) && (num > -1 * ZERO_TOLERANCE));
225+
}
226+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /