Skip to content

Commit 712ff7e

Browse files
committed
Merge remote-tracking branch 'goog/master' into github_live
New samples for the Text Detection library in the Android Mobile Vision API.
2 parents 8b983c9 + 2012092 commit 712ff7e

File tree

88 files changed

+9978
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+9978
-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>
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)