Remove lite/examples/android example
This example has been migrated to the new TensorFlow Examples repo @ https://github.com/tensorflow/examples/tree/master/lite. RELNOTES=Removed TensorFlow Lite Android example (moved to new examples repo). PiperOrigin-RevId: 246173733
This commit is contained in:
parent
935c86ac55
commit
195678bf99
tensorflow
examples/android/src/org/tensorflow/demo
lite/examples/android
BUILDandroid.iml
app
README.mdbuild.gradledownload-models.gradle
build.gradlesettings.gradlesrc/main
AndroidManifest.xml
assets
java/org/tensorflow/demo
AutoFitTextureView.javaCameraActivity.javaCameraConnectionFragment.javaClassifier.javaClassifierActivity.javaDetectorActivity.javaLegacyCameraConnectionFragment.javaOverlayView.javaRecognitionScoreView.javaRecognizeCommands.javaResultsView.javaSpeechActivity.javaTFLiteImageClassifier.javaTFLiteObjectDetectionAPIModel.java
env
tracking
res
animator
drawable-hdpi
drawable-mdpi
drawable-xhdpi
drawable-xxhdpi
drawable
layout
activity_camera.xmlactivity_speech.xmlcamera_connection_fragment.xmlcamera_connection_fragment_stylize.xmlcamera_connection_fragment_tracking.xmllist_text_item.xml
values-sw600dp
values-v11
values-v14
values-v21
values
tools
@ -157,7 +157,7 @@ public class DetectorActivity extends CameraActivity implements OnImageAvailable
|
||||
getAssets(), TF_OD_API_MODEL_FILE, TF_OD_API_LABELS_FILE, TF_OD_API_INPUT_SIZE);
|
||||
cropSize = TF_OD_API_INPUT_SIZE;
|
||||
} catch (final IOException e) {
|
||||
LOGGER.e("Exception initializing classifier!", e);
|
||||
LOGGER.e(e, "Exception initializing classifier!");
|
||||
Toast toast =
|
||||
Toast.makeText(
|
||||
getApplicationContext(), "Classifier could not be initialized", Toast.LENGTH_SHORT);
|
||||
|
@ -1,61 +0,0 @@
|
||||
# Description:
|
||||
# TensorFlow camera demo app for Android.
|
||||
|
||||
load("@build_bazel_rules_android//android:rules.bzl", "android_binary")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"]) # Apache 2.0
|
||||
|
||||
exports_files(["LICENSE"])
|
||||
|
||||
# Build the demo native demo lib from the original directory to reduce code
|
||||
# reuse. Note that the Java counterparts (ObjectTracker.java and
|
||||
# ImageUtils.java) are still duplicated.
|
||||
cc_library(
|
||||
name = "tensorflow_native_libs",
|
||||
srcs = [
|
||||
"//tensorflow/examples/android:libtensorflow_demo.so",
|
||||
],
|
||||
tags = [
|
||||
"manual",
|
||||
"notap",
|
||||
],
|
||||
)
|
||||
|
||||
android_binary(
|
||||
name = "tflite_demo",
|
||||
srcs = glob([
|
||||
"app/src/main/java/**/*.java",
|
||||
]),
|
||||
aapt_version = "aapt",
|
||||
# Package assets from assets dir as well as all model targets.
|
||||
# Remove undesired models (and corresponding Activities in source)
|
||||
# to reduce APK size.
|
||||
assets = [
|
||||
"//tensorflow/lite/examples/android/app/src/main/assets:labels_mobilenet_quant_v1_224.txt",
|
||||
"@tflite_mobilenet_quant//:mobilenet_v1_1.0_224_quant.tflite",
|
||||
"@tflite_conv_actions_frozen//:conv_actions_frozen.tflite",
|
||||
"//tensorflow/lite/examples/android/app/src/main/assets:conv_actions_labels.txt",
|
||||
"@tflite_mobilenet_ssd//:mobilenet_ssd.tflite",
|
||||
"@tflite_mobilenet_ssd_quant//:detect.tflite",
|
||||
"//tensorflow/lite/examples/android/app/src/main/assets:box_priors.txt",
|
||||
"//tensorflow/lite/examples/android/app/src/main/assets:labelmap.txt",
|
||||
],
|
||||
assets_dir = "",
|
||||
custom_package = "org.tensorflow.lite.demo",
|
||||
inline_constants = 1,
|
||||
manifest = "app/src/main/AndroidManifest.xml",
|
||||
nocompress_extensions = [
|
||||
".tflite",
|
||||
],
|
||||
resource_files = glob(["app/src/main/res/**"]),
|
||||
tags = [
|
||||
"manual",
|
||||
"notap",
|
||||
],
|
||||
deps = [
|
||||
":tensorflow_native_libs",
|
||||
"//tensorflow/lite/java:tensorflowlite",
|
||||
],
|
||||
)
|
@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.id="android" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="java-gradle" name="Java-Gradle">
|
||||
<configuration>
|
||||
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
|
||||
<option name="BUILDABLE" value="false" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
@ -1,54 +1,9 @@
|
||||
# TF Lite Android App Example
|
||||
# TF Lite Android Example (Deprecated)
|
||||
|
||||
A simple Android example that demonstrates image classification and object
|
||||
detection using the camera, as well as speech recognition using the microphone.
|
||||
This example has been moved to the new
|
||||
[TensorFlow examples repo](https://github.com/tensorflow/examples), and split
|
||||
into several distinct examples:
|
||||
|
||||
## Building in Android Studio with TensorFlow Lite AAR from JCenter.
|
||||
The build.gradle is configured to use TensorFlow Lite's nightly build.
|
||||
|
||||
If you see a build error related to compatibility with Tensorflow Lite's Java
|
||||
API (example: method X is undefined for type Interpreter), there has likely been
|
||||
a backwards compatible change to the API. You will need to pull new app code
|
||||
that's compatible with the nightly build and may need to first wait a few days
|
||||
for our external and internal code to merge.
|
||||
|
||||
## Building from Source with Bazel
|
||||
|
||||
1. Follow the [Bazel steps for the TF Demo App](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/android#bazel):
|
||||
|
||||
1. [Install Bazel and Android Prerequisites](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/android#install-bazel-and-android-prerequisites).
|
||||
It's easiest with Android Studio.
|
||||
|
||||
- You'll need at least SDK version 23.
|
||||
- Make sure to install the latest version of Bazel. Some distributions
|
||||
ship with Bazel 0.5.4, which is too old.
|
||||
- Bazel requires Android Build Tools `26.0.1` or higher.
|
||||
- You also need to install the Android Support Repository, available
|
||||
through Android Studio under `Android SDK Manager -> SDK Tools ->
|
||||
Android Support Repository`.
|
||||
|
||||
2. [Edit your `WORKSPACE`](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/android#edit-workspace)
|
||||
to add SDK and NDK targets.
|
||||
|
||||
NOTE: As long as you have the SDK and NDK installed, the `./configure`
|
||||
script will create these rules for you. Answer "Yes" when the script asks
|
||||
to automatically configure the `./WORKSPACE`.
|
||||
|
||||
- Make sure the `api_level` in `WORKSPACE` is set to an SDK version that
|
||||
you have installed.
|
||||
- By default, Android Studio will install the SDK to `~/Android/Sdk` and
|
||||
the NDK to `~/Android/Sdk/ndk-bundle`.
|
||||
|
||||
2. Build this demo app with Bazel. The demo needs C++11. We configure the fat_apk_cpu flag to package support for 4 hardware variants. You may replace it with --config=android_arm64 on a 64-bit device and --config=android_arm for 32-bit device:
|
||||
|
||||
```shell
|
||||
bazel build -c opt --cxxopt='--std=c++11' --fat_apk_cpu=x86,x86_64,arm64-v8a,armeabi-v7a \
|
||||
//tensorflow/lite/examples/android:tflite_demo
|
||||
```
|
||||
|
||||
3. Install the demo on a
|
||||
[debug-enabled device](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/android#install):
|
||||
|
||||
```shell
|
||||
adb install bazel-bin/tensorflow/lite/examples/android/tflite_demo.apk
|
||||
```
|
||||
* [Image Classification](https://github.com/tensorflow/examples/tree/master/lite/examples/image_classification/android)
|
||||
* [Object Detection](https://github.com/tensorflow/examples/tree/master/lite/examples/object_detection/android)
|
||||
* [Speech Commands](https://github.com/tensorflow/examples/tree/master/lite/examples/speech_commands/android)
|
||||
|
@ -1,50 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
// import DownloadModels task
|
||||
project.ext.ASSET_DIR = projectDir.toString() + '/src/main/assets'
|
||||
project.ext.TMP_DIR = project.buildDir.toString() + '/downloads'
|
||||
|
||||
// Download default models; if you wish to use your own models then
|
||||
// place them in the "assets" directory and comment out this line.
|
||||
apply from: "download-models.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion 26
|
||||
buildToolsVersion '28.0.3'
|
||||
defaultConfig {
|
||||
applicationId "org.tensorflow.lite.demo"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 26
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
aaptOptions {
|
||||
noCompress "tflite"
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://google.bintray.com/tensorflow'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'org.tensorflow:tensorflow-lite:0.0.0-nightly'
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
/*
|
||||
* download-models.gradle
|
||||
* Downloads model files from ${MODEL_URL} into application's asset folder
|
||||
* Input:
|
||||
* project.ext.TMP_DIR: absolute path to hold downloaded zip files
|
||||
* project.ext.ASSET_DIR: absolute path to save unzipped model files
|
||||
* Output:
|
||||
* 3 model files will be downloaded into given folder of ext.ASSET_DIR
|
||||
*/
|
||||
// hard coded model files
|
||||
|
||||
def models = ['https://storage.googleapis.com/download.tensorflow.org/models/tflite/conv_actions_tflite.zip',
|
||||
'https://storage.googleapis.com/download.tensorflow.org/models/tflite/mobilenet_ssd_tflite_v1.zip',
|
||||
'https://storage.googleapis.com/download.tensorflow.org/models/tflite/coco_ssd_mobilenet_v1_1.0_quant_2018_06_29.zip',
|
||||
'http://download.tensorflow.org/models/mobilenet_v1_2018_02_22/mobilenet_v1_1.0_224.tgz',
|
||||
'http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_1.0_224_quant.tgz']
|
||||
|
||||
// Root URL for model archives
|
||||
def MODEL_URL = 'https://storage.googleapis.com/download.tensorflow.org/models/tflite'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'de.undercouch:gradle-download-task:3.2.0'
|
||||
}
|
||||
}
|
||||
|
||||
import de.undercouch.gradle.tasks.download.Download
|
||||
task downloadFile(type: Download){
|
||||
for (modelUrl in models) {
|
||||
def localFile = modelUrl.split("/")[-1]
|
||||
println "Downloading ${localFile} from ${modelUrl}"
|
||||
src modelUrl
|
||||
}
|
||||
|
||||
dest new File(project.ext.TMP_DIR)
|
||||
overwrite true
|
||||
}
|
||||
|
||||
task extractModels(type: Copy) {
|
||||
for (f in models) {
|
||||
def localFile = f.split("/")[-1]
|
||||
def localExt = localFile.split("[.]")[-1]
|
||||
if (localExt == "tgz") {
|
||||
from tarTree(project.ext.TMP_DIR + '/' + localFile)
|
||||
} else {
|
||||
from zipTree(project.ext.TMP_DIR + '/' + localFile)
|
||||
}
|
||||
}
|
||||
|
||||
into file(project.ext.ASSET_DIR)
|
||||
fileMode 0644
|
||||
exclude '**/LICENSE'
|
||||
|
||||
def needDownload = false
|
||||
for (f in models) {
|
||||
def localFile = f.split("/")[-1]
|
||||
if (!(new File(project.ext.TMP_DIR + '/' + localFile)).exists()) {
|
||||
needDownload = true
|
||||
}
|
||||
}
|
||||
|
||||
if (needDownload) {
|
||||
dependsOn downloadFile
|
||||
}
|
||||
}
|
||||
|
||||
tasks.whenTaskAdded { task ->
|
||||
if (task.name == 'assembleDebug') {
|
||||
task.dependsOn 'extractModels'
|
||||
}
|
||||
if (task.name == 'assembleRelease') {
|
||||
task.dependsOn 'extractModels'
|
||||
}
|
||||
}
|
||||
|
@ -1,60 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.tensorflow.lite.demo">
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
|
||||
<application android:allowBackup="true"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:theme="@style/MaterialTheme">
|
||||
|
||||
<activity android:name="org.tensorflow.demo.ClassifierActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:label="@string/activity_name_classification">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name="org.tensorflow.demo.DetectorActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:label="@string/activity_name_detection">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name="org.tensorflow.demo.SpeechActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:label="@string/activity_name_speech">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -1,12 +0,0 @@
|
||||
_silence_
|
||||
_unknown_
|
||||
yes
|
||||
no
|
||||
up
|
||||
down
|
||||
left
|
||||
right
|
||||
on
|
||||
off
|
||||
stop
|
||||
go
|
@ -1,91 +0,0 @@
|
||||
???
|
||||
person
|
||||
bicycle
|
||||
car
|
||||
motorcycle
|
||||
airplane
|
||||
bus
|
||||
train
|
||||
truck
|
||||
boat
|
||||
traffic light
|
||||
fire hydrant
|
||||
???
|
||||
stop sign
|
||||
parking meter
|
||||
bench
|
||||
bird
|
||||
cat
|
||||
dog
|
||||
horse
|
||||
sheep
|
||||
cow
|
||||
elephant
|
||||
bear
|
||||
zebra
|
||||
giraffe
|
||||
???
|
||||
backpack
|
||||
umbrella
|
||||
???
|
||||
???
|
||||
handbag
|
||||
tie
|
||||
suitcase
|
||||
frisbee
|
||||
skis
|
||||
snowboard
|
||||
sports ball
|
||||
kite
|
||||
baseball bat
|
||||
baseball glove
|
||||
skateboard
|
||||
surfboard
|
||||
tennis racket
|
||||
bottle
|
||||
???
|
||||
wine glass
|
||||
cup
|
||||
fork
|
||||
knife
|
||||
spoon
|
||||
bowl
|
||||
banana
|
||||
apple
|
||||
sandwich
|
||||
orange
|
||||
broccoli
|
||||
carrot
|
||||
hot dog
|
||||
pizza
|
||||
donut
|
||||
cake
|
||||
chair
|
||||
couch
|
||||
potted plant
|
||||
bed
|
||||
???
|
||||
dining table
|
||||
???
|
||||
???
|
||||
toilet
|
||||
???
|
||||
tv
|
||||
laptop
|
||||
mouse
|
||||
remote
|
||||
keyboard
|
||||
cell phone
|
||||
microwave
|
||||
oven
|
||||
toaster
|
||||
sink
|
||||
refrigerator
|
||||
???
|
||||
book
|
||||
clock
|
||||
vase
|
||||
scissors
|
||||
teddy bear
|
||||
hair drier
|
||||
toothbrush
|
@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.tensorflow.demo;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.TextureView;
|
||||
|
||||
/**
|
||||
* A {@link TextureView} that can be adjusted to a specified aspect ratio.
|
||||
*/
|
||||
public class AutoFitTextureView extends TextureView {
|
||||
private int ratioWidth = 0;
|
||||
private int ratioHeight = 0;
|
||||
|
||||
public AutoFitTextureView(final Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public AutoFitTextureView(final Context context, final AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public AutoFitTextureView(final Context context, final AttributeSet attrs, final int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
|
||||
* calculated from the parameters. Note that the actual sizes of parameters don't matter, that
|
||||
* is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
|
||||
*
|
||||
* @param width Relative horizontal size
|
||||
* @param height Relative vertical size
|
||||
*/
|
||||
public void setAspectRatio(final int width, final int height) {
|
||||
if (width < 0 || height < 0) {
|
||||
throw new IllegalArgumentException("Size cannot be negative.");
|
||||
}
|
||||
ratioWidth = width;
|
||||
ratioHeight = height;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
final int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
final int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
if (0 == ratioWidth || 0 == ratioHeight) {
|
||||
setMeasuredDimension(width, height);
|
||||
} else {
|
||||
if (width < height * ratioWidth / ratioHeight) {
|
||||
setMeasuredDimension(width, width * ratioHeight / ratioWidth);
|
||||
} else {
|
||||
setMeasuredDimension(height * ratioWidth / ratioHeight, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,450 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.tensorflow.demo;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.Camera;
|
||||
import android.hardware.camera2.CameraAccessException;
|
||||
import android.hardware.camera2.CameraCharacteristics;
|
||||
import android.hardware.camera2.CameraManager;
|
||||
import android.hardware.camera2.params.StreamConfigurationMap;
|
||||
import android.media.Image;
|
||||
import android.media.Image.Plane;
|
||||
import android.media.ImageReader;
|
||||
import android.media.ImageReader.OnImageAvailableListener;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Trace;
|
||||
import android.util.Size;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.tensorflow.demo.env.ImageUtils;
|
||||
import org.tensorflow.demo.env.Logger;
|
||||
import org.tensorflow.lite.demo.R; // Explicit import needed for internal Google builds.
|
||||
|
||||
public abstract class CameraActivity extends Activity
|
||||
implements OnImageAvailableListener, Camera.PreviewCallback {
|
||||
private static final Logger LOGGER = new Logger();
|
||||
|
||||
private static final int PERMISSIONS_REQUEST = 1;
|
||||
|
||||
private static final String PERMISSION_CAMERA = Manifest.permission.CAMERA;
|
||||
private static final String PERMISSION_STORAGE = Manifest.permission.WRITE_EXTERNAL_STORAGE;
|
||||
|
||||
private boolean debug = false;
|
||||
|
||||
private Handler handler;
|
||||
private HandlerThread handlerThread;
|
||||
private boolean useCamera2API;
|
||||
private boolean isProcessingFrame = false;
|
||||
private byte[][] yuvBytes = new byte[3][];
|
||||
private int[] rgbBytes = null;
|
||||
private int yRowStride;
|
||||
|
||||
protected int previewWidth = 0;
|
||||
protected int previewHeight = 0;
|
||||
|
||||
private Runnable postInferenceCallback;
|
||||
private Runnable imageConverter;
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
LOGGER.d("onCreate " + this);
|
||||
super.onCreate(null);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
|
||||
setContentView(R.layout.activity_camera);
|
||||
|
||||
if (hasPermission()) {
|
||||
setFragment();
|
||||
} else {
|
||||
requestPermission();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected int[] getRgbBytes() {
|
||||
imageConverter.run();
|
||||
return rgbBytes;
|
||||
}
|
||||
|
||||
protected int getLuminanceStride() {
|
||||
return yRowStride;
|
||||
}
|
||||
|
||||
protected byte[] getLuminance() {
|
||||
return yuvBytes[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for android.hardware.Camera API
|
||||
*/
|
||||
@Override
|
||||
public void onPreviewFrame(final byte[] bytes, final Camera camera) {
|
||||
if (isProcessingFrame) {
|
||||
LOGGER.w("Dropping frame!");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Initialize the storage bitmaps once when the resolution is known.
|
||||
if (rgbBytes == null) {
|
||||
Camera.Size previewSize = camera.getParameters().getPreviewSize();
|
||||
previewHeight = previewSize.height;
|
||||
previewWidth = previewSize.width;
|
||||
rgbBytes = new int[previewWidth * previewHeight];
|
||||
onPreviewSizeChosen(new Size(previewSize.width, previewSize.height), 90);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
LOGGER.e(e, "Exception!");
|
||||
return;
|
||||
}
|
||||
|
||||
isProcessingFrame = true;
|
||||
yuvBytes[0] = bytes;
|
||||
yRowStride = previewWidth;
|
||||
|
||||
imageConverter =
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ImageUtils.convertYUV420SPToARGB8888(bytes, previewWidth, previewHeight, rgbBytes);
|
||||
}
|
||||
};
|
||||
|
||||
postInferenceCallback =
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
camera.addCallbackBuffer(bytes);
|
||||
isProcessingFrame = false;
|
||||
}
|
||||
};
|
||||
processImage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for Camera2 API
|
||||
*/
|
||||
@Override
|
||||
public void onImageAvailable(final ImageReader reader) {
|
||||
//We need wait until we have some size from onPreviewSizeChosen
|
||||
if (previewWidth == 0 || previewHeight == 0) {
|
||||
return;
|
||||
}
|
||||
if (rgbBytes == null) {
|
||||
rgbBytes = new int[previewWidth * previewHeight];
|
||||
}
|
||||
try {
|
||||
final Image image = reader.acquireLatestImage();
|
||||
|
||||
if (image == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isProcessingFrame) {
|
||||
image.close();
|
||||
return;
|
||||
}
|
||||
isProcessingFrame = true;
|
||||
Trace.beginSection("imageAvailable");
|
||||
final Plane[] planes = image.getPlanes();
|
||||
fillBytes(planes, yuvBytes);
|
||||
yRowStride = planes[0].getRowStride();
|
||||
final int uvRowStride = planes[1].getRowStride();
|
||||
final int uvPixelStride = planes[1].getPixelStride();
|
||||
|
||||
imageConverter =
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ImageUtils.convertYUV420ToARGB8888(
|
||||
yuvBytes[0],
|
||||
yuvBytes[1],
|
||||
yuvBytes[2],
|
||||
previewWidth,
|
||||
previewHeight,
|
||||
yRowStride,
|
||||
uvRowStride,
|
||||
uvPixelStride,
|
||||
rgbBytes);
|
||||
}
|
||||
};
|
||||
|
||||
postInferenceCallback =
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
image.close();
|
||||
isProcessingFrame = false;
|
||||
}
|
||||
};
|
||||
|
||||
processImage();
|
||||
} catch (final Exception e) {
|
||||
LOGGER.e(e, "Exception!");
|
||||
Trace.endSection();
|
||||
return;
|
||||
}
|
||||
Trace.endSection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onStart() {
|
||||
LOGGER.d("onStart " + this);
|
||||
super.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onResume() {
|
||||
LOGGER.d("onResume " + this);
|
||||
super.onResume();
|
||||
|
||||
handlerThread = new HandlerThread("inference");
|
||||
handlerThread.start();
|
||||
handler = new Handler(handlerThread.getLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onPause() {
|
||||
LOGGER.d("onPause " + this);
|
||||
|
||||
if (!isFinishing()) {
|
||||
LOGGER.d("Requesting finish");
|
||||
finish();
|
||||
}
|
||||
|
||||
handlerThread.quitSafely();
|
||||
try {
|
||||
handlerThread.join();
|
||||
handlerThread = null;
|
||||
handler = null;
|
||||
} catch (final InterruptedException e) {
|
||||
LOGGER.e(e, "Exception!");
|
||||
}
|
||||
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onStop() {
|
||||
LOGGER.d("onStop " + this);
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onDestroy() {
|
||||
LOGGER.d("onDestroy " + this);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
protected synchronized void runInBackground(final Runnable r) {
|
||||
if (handler != null) {
|
||||
handler.post(r);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(
|
||||
final int requestCode, final String[] permissions, final int[] grantResults) {
|
||||
if (requestCode == PERMISSIONS_REQUEST) {
|
||||
if (grantResults.length > 0
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED
|
||||
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
|
||||
setFragment();
|
||||
} else {
|
||||
requestPermission();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasPermission() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
return checkSelfPermission(PERMISSION_CAMERA) == PackageManager.PERMISSION_GRANTED &&
|
||||
checkSelfPermission(PERMISSION_STORAGE) == PackageManager.PERMISSION_GRANTED;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void requestPermission() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (shouldShowRequestPermissionRationale(PERMISSION_CAMERA) ||
|
||||
shouldShowRequestPermissionRationale(PERMISSION_STORAGE)) {
|
||||
Toast.makeText(CameraActivity.this,
|
||||
"Camera AND storage permission are required for this demo", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
requestPermissions(new String[] {PERMISSION_CAMERA, PERMISSION_STORAGE}, PERMISSIONS_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the device supports the required hardware level, or better.
|
||||
private boolean isHardwareLevelSupported(
|
||||
CameraCharacteristics characteristics, int requiredLevel) {
|
||||
int deviceLevel = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
|
||||
if (deviceLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
|
||||
return requiredLevel == deviceLevel;
|
||||
}
|
||||
// deviceLevel is not LEGACY, can use numerical sort
|
||||
return requiredLevel <= deviceLevel;
|
||||
}
|
||||
|
||||
private String chooseCamera() {
|
||||
final CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
|
||||
try {
|
||||
for (final String cameraId : manager.getCameraIdList()) {
|
||||
final CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
|
||||
|
||||
// We don't use a front facing camera in this sample.
|
||||
final Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
|
||||
if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final StreamConfigurationMap map =
|
||||
characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
||||
|
||||
if (map == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fallback to camera1 API for internal cameras that don't have full support.
|
||||
// This should help with legacy situations where using the camera2 API causes
|
||||
// distorted or otherwise broken previews.
|
||||
useCamera2API = (facing == CameraCharacteristics.LENS_FACING_EXTERNAL)
|
||||
|| isHardwareLevelSupported(characteristics,
|
||||
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL);
|
||||
LOGGER.i("Camera API lv2?: %s", useCamera2API);
|
||||
return cameraId;
|
||||
}
|
||||
} catch (CameraAccessException e) {
|
||||
LOGGER.e(e, "Not allowed to access camera");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void setFragment() {
|
||||
String cameraId = chooseCamera();
|
||||
|
||||
Fragment fragment;
|
||||
if (useCamera2API) {
|
||||
CameraConnectionFragment camera2Fragment =
|
||||
CameraConnectionFragment.newInstance(
|
||||
new CameraConnectionFragment.ConnectionCallback() {
|
||||
@Override
|
||||
public void onPreviewSizeChosen(final Size size, final int rotation) {
|
||||
previewHeight = size.getHeight();
|
||||
previewWidth = size.getWidth();
|
||||
CameraActivity.this.onPreviewSizeChosen(size, rotation);
|
||||
}
|
||||
},
|
||||
this,
|
||||
getLayoutId(),
|
||||
getDesiredPreviewFrameSize());
|
||||
|
||||
camera2Fragment.setCamera(cameraId);
|
||||
fragment = camera2Fragment;
|
||||
} else {
|
||||
fragment =
|
||||
new LegacyCameraConnectionFragment(this, getLayoutId(), getDesiredPreviewFrameSize());
|
||||
}
|
||||
|
||||
getFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.container, fragment)
|
||||
.commit();
|
||||
}
|
||||
|
||||
protected void fillBytes(final Plane[] planes, final byte[][] yuvBytes) {
|
||||
// Because of the variable row stride it's not possible to know in
|
||||
// advance the actual necessary dimensions of the yuv planes.
|
||||
for (int i = 0; i < planes.length; ++i) {
|
||||
final ByteBuffer buffer = planes[i].getBuffer();
|
||||
if (yuvBytes[i] == null) {
|
||||
LOGGER.d("Initializing buffer %d at size %d", i, buffer.capacity());
|
||||
yuvBytes[i] = new byte[buffer.capacity()];
|
||||
}
|
||||
buffer.get(yuvBytes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
return debug;
|
||||
}
|
||||
|
||||
public void requestRender() {
|
||||
final OverlayView overlay = (OverlayView) findViewById(R.id.debug_overlay);
|
||||
if (overlay != null) {
|
||||
overlay.postInvalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void addCallback(final OverlayView.DrawCallback callback) {
|
||||
final OverlayView overlay = (OverlayView) findViewById(R.id.debug_overlay);
|
||||
if (overlay != null) {
|
||||
overlay.addCallback(callback);
|
||||
}
|
||||
}
|
||||
|
||||
public void onSetDebug(final boolean debug) {}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(final int keyCode, final KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
||||
debug = !debug;
|
||||
requestRender();
|
||||
onSetDebug(debug);
|
||||
return true;
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
protected void readyForNextImage() {
|
||||
if (postInferenceCallback != null) {
|
||||
postInferenceCallback.run();
|
||||
}
|
||||
}
|
||||
|
||||
protected int getScreenOrientation() {
|
||||
switch (getWindowManager().getDefaultDisplay().getRotation()) {
|
||||
case Surface.ROTATION_270:
|
||||
return 270;
|
||||
case Surface.ROTATION_180:
|
||||
return 180;
|
||||
case Surface.ROTATION_90:
|
||||
return 90;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void processImage();
|
||||
|
||||
protected abstract void onPreviewSizeChosen(final Size size, final int rotation);
|
||||
protected abstract int getLayoutId();
|
||||
protected abstract Size getDesiredPreviewFrameSize();
|
||||
}
|
@ -1,634 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.tensorflow.demo;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.ImageFormat;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.hardware.camera2.CameraAccessException;
|
||||
import android.hardware.camera2.CameraCaptureSession;
|
||||
import android.hardware.camera2.CameraCharacteristics;
|
||||
import android.hardware.camera2.CameraDevice;
|
||||
import android.hardware.camera2.CameraManager;
|
||||
import android.hardware.camera2.CaptureRequest;
|
||||
import android.hardware.camera2.CaptureResult;
|
||||
import android.hardware.camera2.TotalCaptureResult;
|
||||
import android.hardware.camera2.params.StreamConfigurationMap;
|
||||
import android.media.ImageReader;
|
||||
import android.media.ImageReader.OnImageAvailableListener;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Size;
|
||||
import android.util.SparseIntArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Surface;
|
||||
import android.view.TextureView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.tensorflow.demo.env.Logger;
|
||||
import org.tensorflow.lite.demo.R; // Explicit import needed for internal Google builds.
|
||||
|
||||
public class CameraConnectionFragment extends Fragment {
|
||||
private static final Logger LOGGER = new Logger();
|
||||
|
||||
/**
|
||||
* The camera preview size will be chosen to be the smallest frame by pixel size capable of
|
||||
* containing a DESIRED_SIZE x DESIRED_SIZE square.
|
||||
*/
|
||||
private static final int MINIMUM_PREVIEW_SIZE = 320;
|
||||
|
||||
/**
|
||||
* Conversion from screen rotation to JPEG orientation.
|
||||
*/
|
||||
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
|
||||
private static final String FRAGMENT_DIALOG = "dialog";
|
||||
|
||||
static {
|
||||
ORIENTATIONS.append(Surface.ROTATION_0, 90);
|
||||
ORIENTATIONS.append(Surface.ROTATION_90, 0);
|
||||
ORIENTATIONS.append(Surface.ROTATION_180, 270);
|
||||
ORIENTATIONS.append(Surface.ROTATION_270, 180);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link android.view.TextureView.SurfaceTextureListener} handles several lifecycle events on a
|
||||
* {@link TextureView}.
|
||||
*/
|
||||
private final TextureView.SurfaceTextureListener surfaceTextureListener =
|
||||
new TextureView.SurfaceTextureListener() {
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(
|
||||
final SurfaceTexture texture, final int width, final int height) {
|
||||
openCamera(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(
|
||||
final SurfaceTexture texture, final int width, final int height) {
|
||||
configureTransform(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(final SurfaceTexture texture) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(final SurfaceTexture texture) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for Activities to use to initialize their data once the
|
||||
* selected preview size is known.
|
||||
*/
|
||||
public interface ConnectionCallback {
|
||||
void onPreviewSizeChosen(Size size, int cameraRotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* ID of the current {@link CameraDevice}.
|
||||
*/
|
||||
private String cameraId;
|
||||
|
||||
/**
|
||||
* An {@link AutoFitTextureView} for camera preview.
|
||||
*/
|
||||
private AutoFitTextureView textureView;
|
||||
|
||||
/**
|
||||
* A {@link CameraCaptureSession } for camera preview.
|
||||
*/
|
||||
private CameraCaptureSession captureSession;
|
||||
|
||||
/**
|
||||
* A reference to the opened {@link CameraDevice}.
|
||||
*/
|
||||
private CameraDevice cameraDevice;
|
||||
|
||||
/**
|
||||
* The rotation in degrees of the camera sensor from the display.
|
||||
*/
|
||||
private Integer sensorOrientation;
|
||||
|
||||
/**
|
||||
* The {@link android.util.Size} of camera preview.
|
||||
*/
|
||||
private Size previewSize;
|
||||
|
||||
/**
|
||||
* {@link android.hardware.camera2.CameraDevice.StateCallback}
|
||||
* is called when {@link CameraDevice} changes its state.
|
||||
*/
|
||||
private final CameraDevice.StateCallback stateCallback =
|
||||
new CameraDevice.StateCallback() {
|
||||
@Override
|
||||
public void onOpened(final CameraDevice cd) {
|
||||
// This method is called when the camera is opened. We start camera preview here.
|
||||
cameraOpenCloseLock.release();
|
||||
cameraDevice = cd;
|
||||
createCameraPreviewSession();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnected(final CameraDevice cd) {
|
||||
cameraOpenCloseLock.release();
|
||||
cd.close();
|
||||
cameraDevice = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(final CameraDevice cd, final int error) {
|
||||
cameraOpenCloseLock.release();
|
||||
cd.close();
|
||||
cameraDevice = null;
|
||||
final Activity activity = getActivity();
|
||||
if (null != activity) {
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An additional thread for running tasks that shouldn't block the UI.
|
||||
*/
|
||||
private HandlerThread backgroundThread;
|
||||
|
||||
/**
|
||||
* A {@link Handler} for running tasks in the background.
|
||||
*/
|
||||
private Handler backgroundHandler;
|
||||
|
||||
/**
|
||||
* An {@link ImageReader} that handles preview frame capture.
|
||||
*/
|
||||
private ImageReader previewReader;
|
||||
|
||||
/**
|
||||
* {@link android.hardware.camera2.CaptureRequest.Builder} for the camera preview
|
||||
*/
|
||||
private CaptureRequest.Builder previewRequestBuilder;
|
||||
|
||||
/**
|
||||
* {@link CaptureRequest} generated by {@link #previewRequestBuilder}
|
||||
*/
|
||||
private CaptureRequest previewRequest;
|
||||
|
||||
/**
|
||||
* A {@link Semaphore} to prevent the app from exiting before closing the camera.
|
||||
*/
|
||||
private final Semaphore cameraOpenCloseLock = new Semaphore(1);
|
||||
|
||||
/**
|
||||
* A {@link OnImageAvailableListener} to receive frames as they are available.
|
||||
*/
|
||||
private final OnImageAvailableListener imageListener;
|
||||
|
||||
/** The input size in pixels desired by TensorFlow (width and height of a square bitmap). */
|
||||
private final Size inputSize;
|
||||
|
||||
/**
|
||||
* The layout identifier to inflate for this Fragment.
|
||||
*/
|
||||
private final int layout;
|
||||
|
||||
|
||||
private final ConnectionCallback cameraConnectionCallback;
|
||||
|
||||
private CameraConnectionFragment(
|
||||
final ConnectionCallback connectionCallback,
|
||||
final OnImageAvailableListener imageListener,
|
||||
final int layout,
|
||||
final Size inputSize) {
|
||||
this.cameraConnectionCallback = connectionCallback;
|
||||
this.imageListener = imageListener;
|
||||
this.layout = layout;
|
||||
this.inputSize = inputSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a {@link Toast} on the UI thread.
|
||||
*
|
||||
* @param text The message to show
|
||||
*/
|
||||
private void showToast(final String text) {
|
||||
final Activity activity = getActivity();
|
||||
if (activity != null) {
|
||||
activity.runOnUiThread(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(activity, text, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose
|
||||
* width and height are at least as large as the minimum of both, or an exact match if possible.
|
||||
*
|
||||
* @param choices The list of sizes that the camera supports for the intended output class
|
||||
* @param width The minimum desired width
|
||||
* @param height The minimum desired height
|
||||
* @return The optimal {@code Size}, or an arbitrary one if none were big enough
|
||||
*/
|
||||
protected static Size chooseOptimalSize(final Size[] choices, final int width, final int height) {
|
||||
final int minSize = Math.max(Math.min(width, height), MINIMUM_PREVIEW_SIZE);
|
||||
final Size desiredSize = new Size(width, height);
|
||||
|
||||
// Collect the supported resolutions that are at least as big as the preview Surface
|
||||
boolean exactSizeFound = false;
|
||||
final List<Size> bigEnough = new ArrayList<Size>();
|
||||
final List<Size> tooSmall = new ArrayList<Size>();
|
||||
for (final Size option : choices) {
|
||||
if (option.equals(desiredSize)) {
|
||||
// Set the size but don't return yet so that remaining sizes will still be logged.
|
||||
exactSizeFound = true;
|
||||
}
|
||||
|
||||
if (option.getHeight() >= minSize && option.getWidth() >= minSize) {
|
||||
bigEnough.add(option);
|
||||
} else {
|
||||
tooSmall.add(option);
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.i("Desired size: " + desiredSize + ", min size: " + minSize + "x" + minSize);
|
||||
LOGGER.i("Valid preview sizes: [" + TextUtils.join(", ", bigEnough) + "]");
|
||||
LOGGER.i("Rejected preview sizes: [" + TextUtils.join(", ", tooSmall) + "]");
|
||||
|
||||
if (exactSizeFound) {
|
||||
LOGGER.i("Exact size match found.");
|
||||
return desiredSize;
|
||||
}
|
||||
|
||||
// Pick the smallest of those, assuming we found any
|
||||
if (bigEnough.size() > 0) {
|
||||
final Size chosenSize = Collections.min(bigEnough, new CompareSizesByArea());
|
||||
LOGGER.i("Chosen size: " + chosenSize.getWidth() + "x" + chosenSize.getHeight());
|
||||
return chosenSize;
|
||||
} else {
|
||||
LOGGER.e("Couldn't find any suitable preview size");
|
||||
return choices[0];
|
||||
}
|
||||
}
|
||||
|
||||
public static CameraConnectionFragment newInstance(
|
||||
final ConnectionCallback callback,
|
||||
final OnImageAvailableListener imageListener,
|
||||
final int layout,
|
||||
final Size inputSize) {
|
||||
return new CameraConnectionFragment(callback, imageListener, layout, inputSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(
|
||||
final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
|
||||
return inflater.inflate(layout, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(final View view, final Bundle savedInstanceState) {
|
||||
textureView = (AutoFitTextureView) view.findViewById(R.id.texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(final Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
startBackgroundThread();
|
||||
|
||||
// When the screen is turned off and turned back on, the SurfaceTexture is already
|
||||
// available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
|
||||
// a camera and start preview from here (otherwise, we wait until the surface is ready in
|
||||
// the SurfaceTextureListener).
|
||||
if (textureView.isAvailable()) {
|
||||
openCamera(textureView.getWidth(), textureView.getHeight());
|
||||
} else {
|
||||
textureView.setSurfaceTextureListener(surfaceTextureListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
closeCamera();
|
||||
stopBackgroundThread();
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
public void setCamera(String cameraId) {
|
||||
this.cameraId = cameraId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up member variables related to camera.
|
||||
*/
|
||||
private void setUpCameraOutputs() {
|
||||
final Activity activity = getActivity();
|
||||
final CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
|
||||
try {
|
||||
final CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
|
||||
|
||||
final StreamConfigurationMap map =
|
||||
characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
||||
|
||||
sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
|
||||
|
||||
// Danger, W.R.! Attempting to use too large a preview size could exceed the camera
|
||||
// bus' bandwidth limitation, resulting in gorgeous previews but the storage of
|
||||
// garbage capture data.
|
||||
previewSize =
|
||||
chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
|
||||
inputSize.getWidth(),
|
||||
inputSize.getHeight());
|
||||
|
||||
// We fit the aspect ratio of TextureView to the size of preview we picked.
|
||||
final int orientation = getResources().getConfiguration().orientation;
|
||||
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
textureView.setAspectRatio(previewSize.getWidth(), previewSize.getHeight());
|
||||
} else {
|
||||
textureView.setAspectRatio(previewSize.getHeight(), previewSize.getWidth());
|
||||
}
|
||||
} catch (final CameraAccessException e) {
|
||||
LOGGER.e(e, "Exception!");
|
||||
} catch (final NullPointerException e) {
|
||||
// Currently an NPE is thrown when the Camera2API is used but not supported on the
|
||||
// device this code runs.
|
||||
// TODO(andrewharp): abstract ErrorDialog/RuntimeException handling out into new method and
|
||||
// reuse throughout app.
|
||||
ErrorDialog.newInstance(getString(R.string.camera_error))
|
||||
.show(getChildFragmentManager(), FRAGMENT_DIALOG);
|
||||
throw new RuntimeException(getString(R.string.camera_error));
|
||||
}
|
||||
|
||||
cameraConnectionCallback.onPreviewSizeChosen(previewSize, sensorOrientation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the camera specified by {@link CameraConnectionFragment#cameraId}.
|
||||
*/
|
||||
private void openCamera(final int width, final int height) {
|
||||
setUpCameraOutputs();
|
||||
configureTransform(width, height);
|
||||
final Activity activity = getActivity();
|
||||
final CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
|
||||
try {
|
||||
if (!cameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
|
||||
throw new RuntimeException("Time out waiting to lock camera opening.");
|
||||
}
|
||||
manager.openCamera(cameraId, stateCallback, backgroundHandler);
|
||||
} catch (final CameraAccessException e) {
|
||||
LOGGER.e(e, "Exception!");
|
||||
} catch (final InterruptedException e) {
|
||||
throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the current {@link CameraDevice}.
|
||||
*/
|
||||
private void closeCamera() {
|
||||
try {
|
||||
cameraOpenCloseLock.acquire();
|
||||
if (null != captureSession) {
|
||||
captureSession.close();
|
||||
captureSession = null;
|
||||
}
|
||||
if (null != cameraDevice) {
|
||||
cameraDevice.close();
|
||||
cameraDevice = null;
|
||||
}
|
||||
if (null != previewReader) {
|
||||
previewReader.close();
|
||||
previewReader = null;
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
|
||||
} finally {
|
||||
cameraOpenCloseLock.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a background thread and its {@link Handler}.
|
||||
*/
|
||||
private void startBackgroundThread() {
|
||||
backgroundThread = new HandlerThread("ImageListener");
|
||||
backgroundThread.start();
|
||||
backgroundHandler = new Handler(backgroundThread.getLooper());
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the background thread and its {@link Handler}.
|
||||
*/
|
||||
private void stopBackgroundThread() {
|
||||
backgroundThread.quitSafely();
|
||||
try {
|
||||
backgroundThread.join();
|
||||
backgroundThread = null;
|
||||
backgroundHandler = null;
|
||||
} catch (final InterruptedException e) {
|
||||
LOGGER.e(e, "Exception!");
|
||||
}
|
||||
}
|
||||
|
||||
private final CameraCaptureSession.CaptureCallback captureCallback =
|
||||
new CameraCaptureSession.CaptureCallback() {
|
||||
@Override
|
||||
public void onCaptureProgressed(
|
||||
final CameraCaptureSession session,
|
||||
final CaptureRequest request,
|
||||
final CaptureResult partialResult) {}
|
||||
|
||||
@Override
|
||||
public void onCaptureCompleted(
|
||||
final CameraCaptureSession session,
|
||||
final CaptureRequest request,
|
||||
final TotalCaptureResult result) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new {@link CameraCaptureSession} for camera preview.
|
||||
*/
|
||||
private void createCameraPreviewSession() {
|
||||
try {
|
||||
final SurfaceTexture texture = textureView.getSurfaceTexture();
|
||||
assert texture != null;
|
||||
|
||||
// We configure the size of default buffer to be the size of camera preview we want.
|
||||
texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
|
||||
|
||||
// This is the output Surface we need to start preview.
|
||||
final Surface surface = new Surface(texture);
|
||||
|
||||
// We set up a CaptureRequest.Builder with the output Surface.
|
||||
previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
|
||||
previewRequestBuilder.addTarget(surface);
|
||||
|
||||
LOGGER.i("Opening camera preview: " + previewSize.getWidth() + "x" + previewSize.getHeight());
|
||||
|
||||
// Create the reader for the preview frames.
|
||||
previewReader =
|
||||
ImageReader.newInstance(
|
||||
previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2);
|
||||
|
||||
previewReader.setOnImageAvailableListener(imageListener, backgroundHandler);
|
||||
previewRequestBuilder.addTarget(previewReader.getSurface());
|
||||
|
||||
// Here, we create a CameraCaptureSession for camera preview.
|
||||
cameraDevice.createCaptureSession(
|
||||
Arrays.asList(surface, previewReader.getSurface()),
|
||||
new CameraCaptureSession.StateCallback() {
|
||||
|
||||
@Override
|
||||
public void onConfigured(final CameraCaptureSession cameraCaptureSession) {
|
||||
// The camera is already closed
|
||||
if (null == cameraDevice) {
|
||||
return;
|
||||
}
|
||||
|
||||
// When the session is ready, we start displaying the preview.
|
||||
captureSession = cameraCaptureSession;
|
||||
try {
|
||||
// Auto focus should be continuous for camera preview.
|
||||
previewRequestBuilder.set(
|
||||
CaptureRequest.CONTROL_AF_MODE,
|
||||
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
|
||||
// Flash is automatically enabled when necessary.
|
||||
previewRequestBuilder.set(
|
||||
CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
|
||||
|
||||
// Finally, we start displaying the camera preview.
|
||||
previewRequest = previewRequestBuilder.build();
|
||||
captureSession.setRepeatingRequest(
|
||||
previewRequest, captureCallback, backgroundHandler);
|
||||
} catch (final CameraAccessException e) {
|
||||
LOGGER.e(e, "Exception!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigureFailed(final CameraCaptureSession cameraCaptureSession) {
|
||||
showToast("Failed");
|
||||
}
|
||||
},
|
||||
null);
|
||||
} catch (final CameraAccessException e) {
|
||||
LOGGER.e(e, "Exception!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`.
|
||||
* This method should be called after the camera preview size is determined in
|
||||
* setUpCameraOutputs and also the size of `mTextureView` is fixed.
|
||||
*
|
||||
* @param viewWidth The width of `mTextureView`
|
||||
* @param viewHeight The height of `mTextureView`
|
||||
*/
|
||||
private void configureTransform(final int viewWidth, final int viewHeight) {
|
||||
final Activity activity = getActivity();
|
||||
if (null == textureView || null == previewSize || null == activity) {
|
||||
return;
|
||||
}
|
||||
final int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
|
||||
final Matrix matrix = new Matrix();
|
||||
final RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
|
||||
final RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth());
|
||||
final float centerX = viewRect.centerX();
|
||||
final float centerY = viewRect.centerY();
|
||||
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
|
||||
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
|
||||
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
|
||||
final float scale =
|
||||
Math.max(
|
||||
(float) viewHeight / previewSize.getHeight(),
|
||||
(float) viewWidth / previewSize.getWidth());
|
||||
matrix.postScale(scale, scale, centerX, centerY);
|
||||
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
|
||||
} else if (Surface.ROTATION_180 == rotation) {
|
||||
matrix.postRotate(180, centerX, centerY);
|
||||
}
|
||||
textureView.setTransform(matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two {@code Size}s based on their areas.
|
||||
*/
|
||||
static class CompareSizesByArea implements Comparator<Size> {
|
||||
@Override
|
||||
public int compare(final Size lhs, final Size rhs) {
|
||||
// We cast here to ensure the multiplications won't overflow
|
||||
return Long.signum(
|
||||
(long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an error message dialog.
|
||||
*/
|
||||
public static class ErrorDialog extends DialogFragment {
|
||||
private static final String ARG_MESSAGE = "message";
|
||||
|
||||
public static ErrorDialog newInstance(final String message) {
|
||||
final ErrorDialog dialog = new ErrorDialog();
|
||||
final Bundle args = new Bundle();
|
||||
args.putString(ARG_MESSAGE, message);
|
||||
dialog.setArguments(args);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(final Bundle savedInstanceState) {
|
||||
final Activity activity = getActivity();
|
||||
return new AlertDialog.Builder(activity)
|
||||
.setMessage(getArguments().getString(ARG_MESSAGE))
|
||||
.setPositiveButton(
|
||||
android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(final DialogInterface dialogInterface, final int i) {
|
||||
activity.finish();
|
||||
}
|
||||
})
|
||||
.create();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
==============================================================================*/
|
||||
|
||||
package org.tensorflow.demo;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.RectF;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Generic interface for interacting with different recognition engines.
|
||||
*/
|
||||
public interface Classifier {
|
||||
/**
|
||||
* An immutable result returned by a Classifier describing what was recognized.
|
||||
*/
|
||||
public class Recognition {
|
||||
/**
|
||||
* A unique identifier for what has been recognized. Specific to the class, not the instance of
|
||||
* the object.
|
||||
*/
|
||||
private final String id;
|
||||
|
||||
/**
|
||||
* Display name for the recognition.
|
||||
*/
|
||||
private final String title;
|
||||
|
||||
/**
|
||||
* A sortable score for how good the recognition is relative to others. Higher should be better.
|
||||
*/
|
||||
private final Float confidence;
|
||||
|
||||
/** Optional location within the source image for the location of the recognized object. */
|
||||
private RectF location;
|
||||
|
||||
public Recognition(
|
||||
final String id, final String title, final Float confidence, final RectF location) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.confidence = confidence;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public Float getConfidence() {
|
||||
return confidence;
|
||||
}
|
||||
|
||||
public RectF getLocation() {
|
||||
return new RectF(location);
|
||||
}
|
||||
|
||||
public void setLocation(RectF location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String resultString = "";
|
||||
if (id != null) {
|
||||
resultString += "[" + id + "] ";
|
||||
}
|
||||
|
||||
if (title != null) {
|
||||
resultString += title + " ";
|
||||
}
|
||||
|
||||
if (confidence != null) {
|
||||
resultString += String.format("(%.1f%%) ", confidence * 100.0f);
|
||||
}
|
||||
|
||||
if (location != null) {
|
||||
resultString += location + " ";
|
||||
}
|
||||
|
||||
return resultString.trim();
|
||||
}
|
||||
}
|
||||
|
||||
List<Recognition> recognizeImage(Bitmap bitmap);
|
||||
|
||||
void enableStatLogging(final boolean debug);
|
||||
|
||||
String getStatString();
|
||||
|
||||
void close();
|
||||
}
|
@ -1,197 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.tensorflow.demo;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Typeface;
|
||||
import android.media.ImageReader.OnImageAvailableListener;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Size;
|
||||
import android.util.TypedValue;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
import org.tensorflow.demo.OverlayView.DrawCallback;
|
||||
import org.tensorflow.demo.env.BorderedText;
|
||||
import org.tensorflow.demo.env.ImageUtils;
|
||||
import org.tensorflow.demo.env.Logger;
|
||||
import org.tensorflow.lite.demo.R; // Explicit import needed for internal Google builds.
|
||||
|
||||
public class ClassifierActivity extends CameraActivity implements OnImageAvailableListener {
|
||||
private static final Logger LOGGER = new Logger();
|
||||
|
||||
protected static final boolean SAVE_PREVIEW_BITMAP = false;
|
||||
|
||||
private ResultsView resultsView;
|
||||
|
||||
private Bitmap rgbFrameBitmap = null;
|
||||
private Bitmap croppedBitmap = null;
|
||||
private Bitmap cropCopyBitmap = null;
|
||||
|
||||
private long lastProcessingTimeMs;
|
||||
|
||||
// These are the settings for the original v1 Inception model. If you want to
|
||||
// use a model that's been produced from the TensorFlow for Poets codelab,
|
||||
// you'll need to set IMAGE_SIZE = 299, IMAGE_MEAN = 128, IMAGE_STD = 128,
|
||||
// INPUT_NAME = "Mul", and OUTPUT_NAME = "final_result".
|
||||
// You'll also need to update the MODEL_FILE and LABEL_FILE paths to point to
|
||||
// the ones you produced.
|
||||
//
|
||||
// To use v3 Inception model, strip the DecodeJpeg Op from your retrained
|
||||
// model first:
|
||||
//
|
||||
// python strip_unused.py \
|
||||
// --input_graph=<retrained-pb-file> \
|
||||
// --output_graph=<your-stripped-pb-file> \
|
||||
// --input_node_names="Mul" \
|
||||
// --output_node_names="final_result" \
|
||||
// --input_binary=true
|
||||
private static final int INPUT_SIZE = 224;
|
||||
|
||||
private static final String MODEL_FILE = "mobilenet_v1_1.0_224_quant.tflite";
|
||||
private static final String LABEL_FILE = "labels_mobilenet_quant_v1_224.txt";
|
||||
|
||||
private static final boolean MAINTAIN_ASPECT = true;
|
||||
|
||||
private static final Size DESIRED_PREVIEW_SIZE = new Size(640, 480);
|
||||
|
||||
|
||||
private Integer sensorOrientation;
|
||||
private Classifier classifier;
|
||||
private Matrix frameToCropTransform;
|
||||
private Matrix cropToFrameTransform;
|
||||
|
||||
private BorderedText borderedText;
|
||||
|
||||
@Override
|
||||
protected int getLayoutId() {
|
||||
return R.layout.camera_connection_fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Size getDesiredPreviewFrameSize() {
|
||||
return DESIRED_PREVIEW_SIZE;
|
||||
}
|
||||
|
||||
private static final float TEXT_SIZE_DIP = 10;
|
||||
|
||||
@Override
|
||||
public void onPreviewSizeChosen(final Size size, final int rotation) {
|
||||
final float textSizePx = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, TEXT_SIZE_DIP, getResources().getDisplayMetrics());
|
||||
borderedText = new BorderedText(textSizePx);
|
||||
borderedText.setTypeface(Typeface.MONOSPACE);
|
||||
|
||||
classifier = TFLiteImageClassifier.create(getAssets(), MODEL_FILE, LABEL_FILE, INPUT_SIZE);
|
||||
|
||||
previewWidth = size.getWidth();
|
||||
previewHeight = size.getHeight();
|
||||
|
||||
sensorOrientation = rotation - getScreenOrientation();
|
||||
LOGGER.i("Camera orientation relative to screen canvas: %d", sensorOrientation);
|
||||
|
||||
LOGGER.i("Initializing at size %dx%d", previewWidth, previewHeight);
|
||||
rgbFrameBitmap = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
|
||||
croppedBitmap = Bitmap.createBitmap(INPUT_SIZE, INPUT_SIZE, Config.ARGB_8888);
|
||||
|
||||
frameToCropTransform = ImageUtils.getTransformationMatrix(
|
||||
previewWidth, previewHeight,
|
||||
INPUT_SIZE, INPUT_SIZE,
|
||||
sensorOrientation, MAINTAIN_ASPECT);
|
||||
|
||||
cropToFrameTransform = new Matrix();
|
||||
frameToCropTransform.invert(cropToFrameTransform);
|
||||
|
||||
addCallback(
|
||||
new DrawCallback() {
|
||||
@Override
|
||||
public void drawCallback(final Canvas canvas) {
|
||||
renderDebug(canvas);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processImage() {
|
||||
rgbFrameBitmap.setPixels(getRgbBytes(), 0, previewWidth, 0, 0, previewWidth, previewHeight);
|
||||
final Canvas canvas = new Canvas(croppedBitmap);
|
||||
canvas.drawBitmap(rgbFrameBitmap, frameToCropTransform, null);
|
||||
|
||||
// For examining the actual TF input.
|
||||
if (SAVE_PREVIEW_BITMAP) {
|
||||
ImageUtils.saveBitmap(croppedBitmap);
|
||||
}
|
||||
runInBackground(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final long startTime = SystemClock.uptimeMillis();
|
||||
final List<Classifier.Recognition> results = classifier.recognizeImage(croppedBitmap);
|
||||
lastProcessingTimeMs = SystemClock.uptimeMillis() - startTime;
|
||||
LOGGER.i("Detect: %s", results);
|
||||
cropCopyBitmap = Bitmap.createBitmap(croppedBitmap);
|
||||
if (resultsView == null) {
|
||||
resultsView = (ResultsView) findViewById(R.id.results);
|
||||
}
|
||||
resultsView.setResults(results);
|
||||
requestRender();
|
||||
readyForNextImage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetDebug(boolean debug) {
|
||||
classifier.enableStatLogging(debug);
|
||||
}
|
||||
|
||||
private void renderDebug(final Canvas canvas) {
|
||||
if (!isDebug()) {
|
||||
return;
|
||||
}
|
||||
final Bitmap copy = cropCopyBitmap;
|
||||
if (copy != null) {
|
||||
final Matrix matrix = new Matrix();
|
||||
final float scaleFactor = 2;
|
||||
matrix.postScale(scaleFactor, scaleFactor);
|
||||
matrix.postTranslate(
|
||||
canvas.getWidth() - copy.getWidth() * scaleFactor,
|
||||
canvas.getHeight() - copy.getHeight() * scaleFactor);
|
||||
canvas.drawBitmap(copy, matrix, new Paint());
|
||||
|
||||
final Vector<String> lines = new Vector<String>();
|
||||
if (classifier != null) {
|
||||
String statString = classifier.getStatString();
|
||||
String[] statLines = statString.split("\n");
|
||||
for (String line : statLines) {
|
||||
lines.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
lines.add("Frame: " + previewWidth + "x" + previewHeight);
|
||||
lines.add("Crop: " + copy.getWidth() + "x" + copy.getHeight());
|
||||
lines.add("View: " + canvas.getWidth() + "x" + canvas.getHeight());
|
||||
lines.add("Rotation: " + sensorOrientation);
|
||||
lines.add("Inference time: " + lastProcessingTimeMs + "ms");
|
||||
|
||||
borderedText.drawLines(canvas, 10, canvas.getHeight() - 10, lines);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,301 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.tensorflow.demo;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Typeface;
|
||||
import android.media.ImageReader.OnImageAvailableListener;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Size;
|
||||
import android.util.TypedValue;
|
||||
import android.widget.Toast;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
import org.tensorflow.demo.OverlayView.DrawCallback;
|
||||
import org.tensorflow.demo.env.BorderedText;
|
||||
import org.tensorflow.demo.env.ImageUtils;
|
||||
import org.tensorflow.demo.env.Logger;
|
||||
import org.tensorflow.demo.tracking.MultiBoxTracker;
|
||||
import org.tensorflow.lite.demo.R; // Explicit import needed for internal Google builds.
|
||||
|
||||
/**
|
||||
* An activity that uses a TensorFlowMultiBoxDetector and ObjectTracker to detect and then track
|
||||
* objects.
|
||||
*/
|
||||
public class DetectorActivity extends CameraActivity implements OnImageAvailableListener {
|
||||
private static final Logger LOGGER = new Logger();
|
||||
|
||||
// Configuration values for the prepackaged SSD model.
|
||||
private static final int TF_OD_API_INPUT_SIZE = 300;
|
||||
private static final boolean TF_OD_API_IS_QUANTIZED = true;
|
||||
private static final String TF_OD_API_MODEL_FILE = "detect.tflite";
|
||||
private static final String TF_OD_API_LABELS_FILE = "labelmap.txt";
|
||||
|
||||
// Which detection model to use: by default uses Tensorflow Object Detection API frozen
|
||||
// checkpoints.
|
||||
private enum DetectorMode {
|
||||
TF_OD_API;
|
||||
}
|
||||
|
||||
private static final DetectorMode MODE = DetectorMode.TF_OD_API;
|
||||
|
||||
// Minimum detection confidence to track a detection.
|
||||
private static final float MINIMUM_CONFIDENCE_TF_OD_API = 0.6f;
|
||||
|
||||
private static final boolean MAINTAIN_ASPECT = false;
|
||||
|
||||
private static final Size DESIRED_PREVIEW_SIZE = new Size(640, 480);
|
||||
|
||||
private static final boolean SAVE_PREVIEW_BITMAP = false;
|
||||
private static final float TEXT_SIZE_DIP = 10;
|
||||
|
||||
private Integer sensorOrientation;
|
||||
|
||||
private Classifier detector;
|
||||
|
||||
private long lastProcessingTimeMs;
|
||||
private Bitmap rgbFrameBitmap = null;
|
||||
private Bitmap croppedBitmap = null;
|
||||
private Bitmap cropCopyBitmap = null;
|
||||
|
||||
private boolean computingDetection = false;
|
||||
|
||||
private long timestamp = 0;
|
||||
|
||||
private Matrix frameToCropTransform;
|
||||
private Matrix cropToFrameTransform;
|
||||
|
||||
private MultiBoxTracker tracker;
|
||||
|
||||
private byte[] luminanceCopy;
|
||||
|
||||
private BorderedText borderedText;
|
||||
@Override
|
||||
public void onPreviewSizeChosen(final Size size, final int rotation) {
|
||||
final float textSizePx =
|
||||
TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, TEXT_SIZE_DIP, getResources().getDisplayMetrics());
|
||||
borderedText = new BorderedText(textSizePx);
|
||||
borderedText.setTypeface(Typeface.MONOSPACE);
|
||||
|
||||
tracker = new MultiBoxTracker(this);
|
||||
|
||||
int cropSize = TF_OD_API_INPUT_SIZE;
|
||||
|
||||
try {
|
||||
detector =
|
||||
TFLiteObjectDetectionAPIModel.create(
|
||||
getAssets(),
|
||||
TF_OD_API_MODEL_FILE,
|
||||
TF_OD_API_LABELS_FILE,
|
||||
TF_OD_API_INPUT_SIZE,
|
||||
TF_OD_API_IS_QUANTIZED);
|
||||
cropSize = TF_OD_API_INPUT_SIZE;
|
||||
} catch (final IOException e) {
|
||||
LOGGER.e("Exception initializing classifier!", e);
|
||||
Toast toast =
|
||||
Toast.makeText(
|
||||
getApplicationContext(), "Classifier could not be initialized", Toast.LENGTH_SHORT);
|
||||
toast.show();
|
||||
finish();
|
||||
}
|
||||
|
||||
|
||||
previewWidth = size.getWidth();
|
||||
previewHeight = size.getHeight();
|
||||
|
||||
sensorOrientation = rotation - getScreenOrientation();
|
||||
LOGGER.i("Camera orientation relative to screen canvas: %d", sensorOrientation);
|
||||
|
||||
LOGGER.i("Initializing at size %dx%d", previewWidth, previewHeight);
|
||||
rgbFrameBitmap = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
|
||||
croppedBitmap = Bitmap.createBitmap(cropSize, cropSize, Config.ARGB_8888);
|
||||
|
||||
frameToCropTransform =
|
||||
ImageUtils.getTransformationMatrix(
|
||||
previewWidth, previewHeight,
|
||||
cropSize, cropSize,
|
||||
sensorOrientation, MAINTAIN_ASPECT);
|
||||
|
||||
cropToFrameTransform = new Matrix();
|
||||
frameToCropTransform.invert(cropToFrameTransform);
|
||||
|
||||
trackingOverlay = (OverlayView) findViewById(R.id.tracking_overlay);
|
||||
trackingOverlay.addCallback(
|
||||
new DrawCallback() {
|
||||
@Override
|
||||
public void drawCallback(final Canvas canvas) {
|
||||
tracker.draw(canvas);
|
||||
if (isDebug()) {
|
||||
tracker.drawDebug(canvas);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
addCallback(
|
||||
new DrawCallback() {
|
||||
@Override
|
||||
public void drawCallback(final Canvas canvas) {
|
||||
if (!isDebug()) {
|
||||
return;
|
||||
}
|
||||
final Bitmap copy = cropCopyBitmap;
|
||||
if (copy == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int backgroundColor = Color.argb(100, 0, 0, 0);
|
||||
canvas.drawColor(backgroundColor);
|
||||
|
||||
final Matrix matrix = new Matrix();
|
||||
final float scaleFactor = 2;
|
||||
matrix.postScale(scaleFactor, scaleFactor);
|
||||
matrix.postTranslate(
|
||||
canvas.getWidth() - copy.getWidth() * scaleFactor,
|
||||
canvas.getHeight() - copy.getHeight() * scaleFactor);
|
||||
canvas.drawBitmap(copy, matrix, new Paint());
|
||||
|
||||
final Vector<String> lines = new Vector<String>();
|
||||
if (detector != null) {
|
||||
final String statString = detector.getStatString();
|
||||
final String[] statLines = statString.split("\n");
|
||||
for (final String line : statLines) {
|
||||
lines.add(line);
|
||||
}
|
||||
}
|
||||
lines.add("");
|
||||
|
||||
lines.add("Frame: " + previewWidth + "x" + previewHeight);
|
||||
lines.add("Crop: " + copy.getWidth() + "x" + copy.getHeight());
|
||||
lines.add("View: " + canvas.getWidth() + "x" + canvas.getHeight());
|
||||
lines.add("Rotation: " + sensorOrientation);
|
||||
lines.add("Inference time: " + lastProcessingTimeMs + "ms");
|
||||
|
||||
borderedText.drawLines(canvas, 10, canvas.getHeight() - 10, lines);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
OverlayView trackingOverlay;
|
||||
|
||||
@Override
|
||||
protected void processImage() {
|
||||
++timestamp;
|
||||
final long currTimestamp = timestamp;
|
||||
byte[] originalLuminance = getLuminance();
|
||||
tracker.onFrame(
|
||||
previewWidth,
|
||||
previewHeight,
|
||||
getLuminanceStride(),
|
||||
sensorOrientation,
|
||||
originalLuminance,
|
||||
timestamp);
|
||||
trackingOverlay.postInvalidate();
|
||||
|
||||
// No mutex needed as this method is not reentrant.
|
||||
if (computingDetection) {
|
||||
readyForNextImage();
|
||||
return;
|
||||
}
|
||||
computingDetection = true;
|
||||
LOGGER.i("Preparing image " + currTimestamp + " for detection in bg thread.");
|
||||
|
||||
rgbFrameBitmap.setPixels(getRgbBytes(), 0, previewWidth, 0, 0, previewWidth, previewHeight);
|
||||
|
||||
if (luminanceCopy == null) {
|
||||
luminanceCopy = new byte[originalLuminance.length];
|
||||
}
|
||||
System.arraycopy(originalLuminance, 0, luminanceCopy, 0, originalLuminance.length);
|
||||
readyForNextImage();
|
||||
|
||||
final Canvas canvas = new Canvas(croppedBitmap);
|
||||
canvas.drawBitmap(rgbFrameBitmap, frameToCropTransform, null);
|
||||
// For examining the actual TF input.
|
||||
if (SAVE_PREVIEW_BITMAP) {
|
||||
ImageUtils.saveBitmap(croppedBitmap);
|
||||
}
|
||||
|
||||
runInBackground(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
LOGGER.i("Running detection on image " + currTimestamp);
|
||||
final long startTime = SystemClock.uptimeMillis();
|
||||
final List<Classifier.Recognition> results = detector.recognizeImage(croppedBitmap);
|
||||
lastProcessingTimeMs = SystemClock.uptimeMillis() - startTime;
|
||||
|
||||
cropCopyBitmap = Bitmap.createBitmap(croppedBitmap);
|
||||
final Canvas canvas = new Canvas(cropCopyBitmap);
|
||||
final Paint paint = new Paint();
|
||||
paint.setColor(Color.RED);
|
||||
paint.setStyle(Style.STROKE);
|
||||
paint.setStrokeWidth(2.0f);
|
||||
|
||||
float minimumConfidence = MINIMUM_CONFIDENCE_TF_OD_API;
|
||||
switch (MODE) {
|
||||
case TF_OD_API:
|
||||
minimumConfidence = MINIMUM_CONFIDENCE_TF_OD_API;
|
||||
break;
|
||||
}
|
||||
|
||||
final List<Classifier.Recognition> mappedRecognitions =
|
||||
new LinkedList<Classifier.Recognition>();
|
||||
|
||||
for (final Classifier.Recognition result : results) {
|
||||
final RectF location = result.getLocation();
|
||||
if (location != null && result.getConfidence() >= minimumConfidence) {
|
||||
canvas.drawRect(location, paint);
|
||||
|
||||
cropToFrameTransform.mapRect(location);
|
||||
result.setLocation(location);
|
||||
mappedRecognitions.add(result);
|
||||
}
|
||||
}
|
||||
|
||||
tracker.trackResults(mappedRecognitions, luminanceCopy, currTimestamp);
|
||||
trackingOverlay.postInvalidate();
|
||||
|
||||
requestRender();
|
||||
computingDetection = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutId() {
|
||||
return R.layout.camera_connection_fragment_tracking;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Size getDesiredPreviewFrameSize() {
|
||||
return DESIRED_PREVIEW_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetDebug(final boolean debug) {
|
||||
detector.enableStatLogging(debug);
|
||||
}
|
||||
}
|
@ -1,216 +0,0 @@
|
||||
package org.tensorflow.demo;
|
||||
|
||||
/*
|
||||
* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.hardware.Camera;
|
||||
import android.hardware.Camera.CameraInfo;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.util.Size;
|
||||
import android.util.SparseIntArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Surface;
|
||||
import android.view.TextureView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import org.tensorflow.demo.env.ImageUtils;
|
||||
import org.tensorflow.demo.env.Logger;
|
||||
import org.tensorflow.lite.demo.R; // Explicit import needed for internal Google builds.
|
||||
|
||||
public class LegacyCameraConnectionFragment extends Fragment {
|
||||
private Camera camera;
|
||||
private static final Logger LOGGER = new Logger();
|
||||
private Camera.PreviewCallback imageListener;
|
||||
private Size desiredSize;
|
||||
|
||||
/**
|
||||
* The layout identifier to inflate for this Fragment.
|
||||
*/
|
||||
private int layout;
|
||||
|
||||
public LegacyCameraConnectionFragment(
|
||||
final Camera.PreviewCallback imageListener, final int layout, final Size desiredSize) {
|
||||
this.imageListener = imageListener;
|
||||
this.layout = layout;
|
||||
this.desiredSize = desiredSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conversion from screen rotation to JPEG orientation.
|
||||
*/
|
||||
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
|
||||
|
||||
static {
|
||||
ORIENTATIONS.append(Surface.ROTATION_0, 90);
|
||||
ORIENTATIONS.append(Surface.ROTATION_90, 0);
|
||||
ORIENTATIONS.append(Surface.ROTATION_180, 270);
|
||||
ORIENTATIONS.append(Surface.ROTATION_270, 180);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link android.view.TextureView.SurfaceTextureListener} handles several lifecycle events on a
|
||||
* {@link TextureView}.
|
||||
*/
|
||||
private final TextureView.SurfaceTextureListener surfaceTextureListener =
|
||||
new TextureView.SurfaceTextureListener() {
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(
|
||||
final SurfaceTexture texture, final int width, final int height) {
|
||||
|
||||
int index = getCameraId();
|
||||
camera = Camera.open(index);
|
||||
|
||||
try {
|
||||
Camera.Parameters parameters = camera.getParameters();
|
||||
List<String> focusModes = parameters.getSupportedFocusModes();
|
||||
if (focusModes != null
|
||||
&& focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
|
||||
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
|
||||
}
|
||||
List<Camera.Size> cameraSizes = parameters.getSupportedPreviewSizes();
|
||||
Size[] sizes = new Size[cameraSizes.size()];
|
||||
int i = 0;
|
||||
for (Camera.Size size : cameraSizes) {
|
||||
sizes[i++] = new Size(size.width, size.height);
|
||||
}
|
||||
Size previewSize =
|
||||
CameraConnectionFragment.chooseOptimalSize(
|
||||
sizes, desiredSize.getWidth(), desiredSize.getHeight());
|
||||
parameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight());
|
||||
camera.setDisplayOrientation(90);
|
||||
camera.setParameters(parameters);
|
||||
camera.setPreviewTexture(texture);
|
||||
} catch (IOException exception) {
|
||||
camera.release();
|
||||
}
|
||||
|
||||
camera.setPreviewCallbackWithBuffer(imageListener);
|
||||
Camera.Size s = camera.getParameters().getPreviewSize();
|
||||
camera.addCallbackBuffer(new byte[ImageUtils.getYUVByteSize(s.height, s.width)]);
|
||||
|
||||
textureView.setAspectRatio(s.height, s.width);
|
||||
|
||||
camera.startPreview();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(
|
||||
final SurfaceTexture texture, final int width, final int height) {}
|
||||
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(final SurfaceTexture texture) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(final SurfaceTexture texture) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* An {@link AutoFitTextureView} for camera preview.
|
||||
*/
|
||||
private AutoFitTextureView textureView;
|
||||
|
||||
/**
|
||||
* An additional thread for running tasks that shouldn't block the UI.
|
||||
*/
|
||||
private HandlerThread backgroundThread;
|
||||
|
||||
@Override
|
||||
public View onCreateView(
|
||||
final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
|
||||
return inflater.inflate(layout, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(final View view, final Bundle savedInstanceState) {
|
||||
textureView = (AutoFitTextureView) view.findViewById(R.id.texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(final Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
startBackgroundThread();
|
||||
// When the screen is turned off and turned back on, the SurfaceTexture is already
|
||||
// available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
|
||||
// a camera and start preview from here (otherwise, we wait until the surface is ready in
|
||||
// the SurfaceTextureListener).
|
||||
|
||||
if (textureView.isAvailable()) {
|
||||
camera.startPreview();
|
||||
} else {
|
||||
textureView.setSurfaceTextureListener(surfaceTextureListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
stopCamera();
|
||||
stopBackgroundThread();
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a background thread and its {@link Handler}.
|
||||
*/
|
||||
private void startBackgroundThread() {
|
||||
backgroundThread = new HandlerThread("CameraBackground");
|
||||
backgroundThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the background thread and its {@link Handler}.
|
||||
*/
|
||||
private void stopBackgroundThread() {
|
||||
backgroundThread.quitSafely();
|
||||
try {
|
||||
backgroundThread.join();
|
||||
backgroundThread = null;
|
||||
} catch (final InterruptedException e) {
|
||||
LOGGER.e(e, "Exception!");
|
||||
}
|
||||
}
|
||||
|
||||
protected void stopCamera() {
|
||||
if (camera != null) {
|
||||
camera.stopPreview();
|
||||
camera.setPreviewCallback(null);
|
||||
camera.release();
|
||||
camera = null;
|
||||
}
|
||||
}
|
||||
|
||||
private int getCameraId() {
|
||||
CameraInfo ci = new CameraInfo();
|
||||
for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
|
||||
Camera.getCameraInfo(i, ci);
|
||||
if (ci.facing == CameraInfo.CAMERA_FACING_BACK)
|
||||
return i;
|
||||
}
|
||||
return -1; // No camera found
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
==============================================================================*/
|
||||
|
||||
package org.tensorflow.demo;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A simple View providing a render callback to other classes.
|
||||
*/
|
||||
public class OverlayView extends View {
|
||||
private final List<DrawCallback> callbacks = new LinkedList<DrawCallback>();
|
||||
|
||||
public OverlayView(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface defining the callback for client classes.
|
||||
*/
|
||||
public interface DrawCallback {
|
||||
public void drawCallback(final Canvas canvas);
|
||||
}
|
||||
|
||||
public void addCallback(final DrawCallback callback) {
|
||||
callbacks.add(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void draw(final Canvas canvas) {
|
||||
for (final DrawCallback callback : callbacks) {
|
||||
callback.drawCallback(canvas);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
==============================================================================*/
|
||||
|
||||
package org.tensorflow.demo;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import java.util.List;
|
||||
import org.tensorflow.demo.Classifier.Recognition;
|
||||
|
||||
public class RecognitionScoreView extends View implements ResultsView {
|
||||
private static final float TEXT_SIZE_DIP = 24;
|
||||
private List<Recognition> results;
|
||||
private final float textSizePx;
|
||||
private final Paint fgPaint;
|
||||
private final Paint bgPaint;
|
||||
|
||||
public RecognitionScoreView(final Context context, final AttributeSet set) {
|
||||
super(context, set);
|
||||
|
||||
textSizePx =
|
||||
TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, TEXT_SIZE_DIP, getResources().getDisplayMetrics());
|
||||
fgPaint = new Paint();
|
||||
fgPaint.setTextSize(textSizePx);
|
||||
|
||||
bgPaint = new Paint();
|
||||
bgPaint.setColor(0xcc4285f4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResults(final List<Recognition> results) {
|
||||
this.results = results;
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(final Canvas canvas) {
|
||||
final int x = 10;
|
||||
int y = (int) (fgPaint.getTextSize() * 1.5f);
|
||||
|
||||
canvas.drawPaint(bgPaint);
|
||||
|
||||
if (results != null) {
|
||||
for (final Recognition recog : results) {
|
||||
canvas.drawText(recog.getTitle() + ": " + recog.getConfidence(), x, y, fgPaint);
|
||||
y += (int) (fgPaint.getTextSize() * 1.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,186 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.tensorflow.demo;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
||||
/** Reads in results from an instantaneous audio recognition model and smoothes them over time. */
|
||||
public class RecognizeCommands {
|
||||
// Configuration settings.
|
||||
private List<String> labels = new ArrayList<String>();
|
||||
private long averageWindowDurationMs;
|
||||
private float detectionThreshold;
|
||||
private int suppressionMs;
|
||||
private int minimumCount;
|
||||
private long minimumTimeBetweenSamplesMs;
|
||||
|
||||
// Working variables.
|
||||
private Deque<Pair<Long, float[]>> previousResults = new ArrayDeque<Pair<Long, float[]>>();
|
||||
private String previousTopLabel;
|
||||
private int labelsCount;
|
||||
private long previousTopLabelTime;
|
||||
private float previousTopLabelScore;
|
||||
|
||||
private static final String SILENCE_LABEL = "_silence_";
|
||||
private static final long MINIMUM_TIME_FRACTION = 4;
|
||||
|
||||
public RecognizeCommands(
|
||||
List<String> inLabels,
|
||||
long inAverageWindowDurationMs,
|
||||
float inDetectionThreshold,
|
||||
int inSuppressionMS,
|
||||
int inMinimumCount,
|
||||
long inMinimumTimeBetweenSamplesMS) {
|
||||
labels = inLabels;
|
||||
averageWindowDurationMs = inAverageWindowDurationMs;
|
||||
detectionThreshold = inDetectionThreshold;
|
||||
suppressionMs = inSuppressionMS;
|
||||
minimumCount = inMinimumCount;
|
||||
labelsCount = inLabels.size();
|
||||
previousTopLabel = SILENCE_LABEL;
|
||||
previousTopLabelTime = Long.MIN_VALUE;
|
||||
previousTopLabelScore = 0.0f;
|
||||
minimumTimeBetweenSamplesMs = inMinimumTimeBetweenSamplesMS;
|
||||
}
|
||||
|
||||
/** Holds information about what's been recognized. */
|
||||
public static class RecognitionResult {
|
||||
public final String foundCommand;
|
||||
public final float score;
|
||||
public final boolean isNewCommand;
|
||||
|
||||
public RecognitionResult(String inFoundCommand, float inScore, boolean inIsNewCommand) {
|
||||
foundCommand = inFoundCommand;
|
||||
score = inScore;
|
||||
isNewCommand = inIsNewCommand;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ScoreForSorting implements Comparable<ScoreForSorting> {
|
||||
public final float score;
|
||||
public final int index;
|
||||
|
||||
public ScoreForSorting(float inScore, int inIndex) {
|
||||
score = inScore;
|
||||
index = inIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ScoreForSorting other) {
|
||||
if (this.score > other.score) {
|
||||
return -1;
|
||||
} else if (this.score < other.score) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RecognitionResult processLatestResults(float[] currentResults, long currentTimeMS) {
|
||||
if (currentResults.length != labelsCount) {
|
||||
throw new RuntimeException(
|
||||
"The results for recognition should contain "
|
||||
+ labelsCount
|
||||
+ " elements, but there are "
|
||||
+ currentResults.length);
|
||||
}
|
||||
|
||||
if ((!previousResults.isEmpty()) && (currentTimeMS < previousResults.getFirst().first)) {
|
||||
throw new RuntimeException(
|
||||
"You must feed results in increasing time order, but received a timestamp of "
|
||||
+ currentTimeMS
|
||||
+ " that was earlier than the previous one of "
|
||||
+ previousResults.getFirst().first);
|
||||
}
|
||||
|
||||
final int howManyResults = previousResults.size();
|
||||
// Ignore any results that are coming in too frequently.
|
||||
if (howManyResults > 1) {
|
||||
final long timeSinceMostRecent = currentTimeMS - previousResults.getLast().first;
|
||||
if (timeSinceMostRecent < minimumTimeBetweenSamplesMs) {
|
||||
return new RecognitionResult(previousTopLabel, previousTopLabelScore, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the latest results to the head of the queue.
|
||||
previousResults.addLast(new Pair<Long, float[]>(currentTimeMS, currentResults));
|
||||
|
||||
// Prune any earlier results that are too old for the averaging window.
|
||||
final long timeLimit = currentTimeMS - averageWindowDurationMs;
|
||||
while (previousResults.getFirst().first < timeLimit) {
|
||||
previousResults.removeFirst();
|
||||
}
|
||||
|
||||
// If there are too few results, assume the result will be unreliable and
|
||||
// bail.
|
||||
final long earliestTime = previousResults.getFirst().first;
|
||||
final long samplesDuration = currentTimeMS - earliestTime;
|
||||
if ((howManyResults < minimumCount)
|
||||
|| (samplesDuration < (averageWindowDurationMs / MINIMUM_TIME_FRACTION))) {
|
||||
Log.v("RecognizeResult", "Too few results");
|
||||
return new RecognitionResult(previousTopLabel, 0.0f, false);
|
||||
}
|
||||
|
||||
// Calculate the average score across all the results in the window.
|
||||
float[] averageScores = new float[labelsCount];
|
||||
for (Pair<Long, float[]> previousResult : previousResults) {
|
||||
final float[] scoresTensor = previousResult.second;
|
||||
int i = 0;
|
||||
while (i < scoresTensor.length) {
|
||||
averageScores[i] += scoresTensor[i] / howManyResults;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the averaged results in descending score order.
|
||||
ScoreForSorting[] sortedAverageScores = new ScoreForSorting[labelsCount];
|
||||
for (int i = 0; i < labelsCount; ++i) {
|
||||
sortedAverageScores[i] = new ScoreForSorting(averageScores[i], i);
|
||||
}
|
||||
Arrays.sort(sortedAverageScores);
|
||||
|
||||
// See if the latest top score is enough to trigger a detection.
|
||||
final int currentTopIndex = sortedAverageScores[0].index;
|
||||
final String currentTopLabel = labels.get(currentTopIndex);
|
||||
final float currentTopScore = sortedAverageScores[0].score;
|
||||
// If we've recently had another label trigger, assume one that occurs too
|
||||
// soon afterwards is a bad result.
|
||||
long timeSinceLastTop;
|
||||
if (previousTopLabel.equals(SILENCE_LABEL) || (previousTopLabelTime == Long.MIN_VALUE)) {
|
||||
timeSinceLastTop = Long.MAX_VALUE;
|
||||
} else {
|
||||
timeSinceLastTop = currentTimeMS - previousTopLabelTime;
|
||||
}
|
||||
boolean isNewCommand;
|
||||
if ((currentTopScore > detectionThreshold) && (timeSinceLastTop > suppressionMs)) {
|
||||
previousTopLabel = currentTopLabel;
|
||||
previousTopLabelTime = currentTimeMS;
|
||||
previousTopLabelScore = currentTopScore;
|
||||
isNewCommand = true;
|
||||
} else {
|
||||
isNewCommand = false;
|
||||
}
|
||||
return new RecognitionResult(currentTopLabel, currentTopScore, isNewCommand);
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
==============================================================================*/
|
||||
|
||||
package org.tensorflow.demo;
|
||||
|
||||
import java.util.List;
|
||||
import org.tensorflow.demo.Classifier.Recognition;
|
||||
|
||||
public interface ResultsView {
|
||||
public void setResults(final List<Recognition> results);
|
||||
}
|
@ -1,381 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* Demonstrates how to run an audio recognition model in Android.
|
||||
|
||||
This example loads a simple speech recognition model trained by the tutorial at
|
||||
https://www.tensorflow.org/tutorials/audio_training
|
||||
|
||||
The model files should be downloaded automatically from the TensorFlow website,
|
||||
but if you have a custom model you can update the LABEL_FILENAME and
|
||||
MODEL_FILENAME constants to point to your own files.
|
||||
|
||||
The example application displays a list view with all of the known audio labels,
|
||||
and highlights each one when it thinks it has detected one through the
|
||||
microphone. The averaging of results to give a more reliable signal happens in
|
||||
the RecognizeCommands helper class.
|
||||
*/
|
||||
|
||||
package org.tensorflow.demo;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.content.res.AssetManager;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.MediaRecorder;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.ListView;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.MappedByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import org.tensorflow.lite.Interpreter;
|
||||
import org.tensorflow.lite.demo.R; // Explicit import needed for internal Google builds.
|
||||
|
||||
/**
|
||||
* An activity that listens for audio and then uses a TensorFlow model to detect particular classes,
|
||||
* by default a small set of action words.
|
||||
*/
|
||||
public class SpeechActivity extends Activity {
|
||||
|
||||
// Constants that control the behavior of the recognition code and model
|
||||
// settings. See the audio recognition tutorial for a detailed explanation of
|
||||
// all these, but you should customize them to match your training settings if
|
||||
// you are running your own model.
|
||||
private static final int SAMPLE_RATE = 16000;
|
||||
private static final int SAMPLE_DURATION_MS = 1000;
|
||||
private static final int RECORDING_LENGTH = (int) (SAMPLE_RATE * SAMPLE_DURATION_MS / 1000);
|
||||
private static final long AVERAGE_WINDOW_DURATION_MS = 500;
|
||||
private static final float DETECTION_THRESHOLD = 0.70f;
|
||||
private static final int SUPPRESSION_MS = 1500;
|
||||
private static final int MINIMUM_COUNT = 3;
|
||||
private static final long MINIMUM_TIME_BETWEEN_SAMPLES_MS = 30;
|
||||
private static final String LABEL_FILENAME = "file:///android_asset/conv_actions_labels.txt";
|
||||
private static final String MODEL_FILENAME = "file:///android_asset/conv_actions_frozen.tflite";
|
||||
|
||||
// UI elements.
|
||||
private static final int REQUEST_RECORD_AUDIO = 13;
|
||||
private Button quitButton;
|
||||
private ListView labelsListView;
|
||||
private static final String LOG_TAG = SpeechActivity.class.getSimpleName();
|
||||
|
||||
// Working variables.
|
||||
short[] recordingBuffer = new short[RECORDING_LENGTH];
|
||||
int recordingOffset = 0;
|
||||
boolean shouldContinue = true;
|
||||
private Thread recordingThread;
|
||||
boolean shouldContinueRecognition = true;
|
||||
private Thread recognitionThread;
|
||||
private final ReentrantLock recordingBufferLock = new ReentrantLock();
|
||||
|
||||
private List<String> labels = new ArrayList<String>();
|
||||
private List<String> displayedLabels = new ArrayList<>();
|
||||
private RecognizeCommands recognizeCommands = null;
|
||||
|
||||
private Interpreter tfLite;
|
||||
|
||||
/** Memory-map the model file in Assets. */
|
||||
private static MappedByteBuffer loadModelFile(AssetManager assets, String modelFilename)
|
||||
throws IOException {
|
||||
AssetFileDescriptor fileDescriptor = assets.openFd(modelFilename);
|
||||
FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());
|
||||
FileChannel fileChannel = inputStream.getChannel();
|
||||
long startOffset = fileDescriptor.getStartOffset();
|
||||
long declaredLength = fileDescriptor.getDeclaredLength();
|
||||
return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
// Set up the UI.
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_speech);
|
||||
quitButton = (Button) findViewById(R.id.quit);
|
||||
quitButton.setOnClickListener(
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
moveTaskToBack(true);
|
||||
android.os.Process.killProcess(android.os.Process.myPid());
|
||||
System.exit(1);
|
||||
}
|
||||
});
|
||||
labelsListView = (ListView) findViewById(R.id.list_view);
|
||||
|
||||
// Load the labels for the model, but only display those that don't start
|
||||
// with an underscore.
|
||||
String actualLabelFilename = LABEL_FILENAME.split("file:///android_asset/", -1)[1];
|
||||
Log.i(LOG_TAG, "Reading labels from: " + actualLabelFilename);
|
||||
BufferedReader br = null;
|
||||
try {
|
||||
br = new BufferedReader(new InputStreamReader(getAssets().open(actualLabelFilename)));
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
labels.add(line);
|
||||
if (line.charAt(0) != '_') {
|
||||
displayedLabels.add(line.substring(0, 1).toUpperCase() + line.substring(1));
|
||||
}
|
||||
}
|
||||
br.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Problem reading label file!", e);
|
||||
}
|
||||
|
||||
// Build a list view based on these labels.
|
||||
ArrayAdapter<String> arrayAdapter =
|
||||
new ArrayAdapter<String>(this, R.layout.list_text_item, displayedLabels);
|
||||
labelsListView.setAdapter(arrayAdapter);
|
||||
|
||||
// Set up an object to smooth recognition results to increase accuracy.
|
||||
recognizeCommands =
|
||||
new RecognizeCommands(
|
||||
labels,
|
||||
AVERAGE_WINDOW_DURATION_MS,
|
||||
DETECTION_THRESHOLD,
|
||||
SUPPRESSION_MS,
|
||||
MINIMUM_COUNT,
|
||||
MINIMUM_TIME_BETWEEN_SAMPLES_MS);
|
||||
|
||||
String actualModelFilename = MODEL_FILENAME.split("file:///android_asset/", -1)[1];
|
||||
try {
|
||||
tfLite = new Interpreter(loadModelFile(getAssets(), actualModelFilename));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
tfLite.resizeInput(0, new int[] {RECORDING_LENGTH, 1});
|
||||
tfLite.resizeInput(1, new int[] {1});
|
||||
|
||||
// Start the recording and recognition threads.
|
||||
requestMicrophonePermission();
|
||||
startRecording();
|
||||
startRecognition();
|
||||
}
|
||||
|
||||
private void requestMicrophonePermission() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
requestPermissions(
|
||||
new String[]{android.Manifest.permission.RECORD_AUDIO}, REQUEST_RECORD_AUDIO);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(
|
||||
int requestCode, String[] permissions, int[] grantResults) {
|
||||
if (requestCode == REQUEST_RECORD_AUDIO
|
||||
&& grantResults.length > 0
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
startRecording();
|
||||
startRecognition();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void startRecording() {
|
||||
if (recordingThread != null) {
|
||||
return;
|
||||
}
|
||||
shouldContinue = true;
|
||||
recordingThread =
|
||||
new Thread(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
record();
|
||||
}
|
||||
});
|
||||
recordingThread.start();
|
||||
}
|
||||
|
||||
public synchronized void stopRecording() {
|
||||
if (recordingThread == null) {
|
||||
return;
|
||||
}
|
||||
shouldContinue = false;
|
||||
recordingThread = null;
|
||||
}
|
||||
|
||||
private void record() {
|
||||
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
|
||||
|
||||
// Estimate the buffer size we'll need for this device.
|
||||
int bufferSize =
|
||||
AudioRecord.getMinBufferSize(
|
||||
SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
|
||||
if (bufferSize == AudioRecord.ERROR || bufferSize == AudioRecord.ERROR_BAD_VALUE) {
|
||||
bufferSize = SAMPLE_RATE * 2;
|
||||
}
|
||||
short[] audioBuffer = new short[bufferSize / 2];
|
||||
|
||||
AudioRecord record =
|
||||
new AudioRecord(
|
||||
MediaRecorder.AudioSource.DEFAULT,
|
||||
SAMPLE_RATE,
|
||||
AudioFormat.CHANNEL_IN_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT,
|
||||
bufferSize);
|
||||
|
||||
if (record.getState() != AudioRecord.STATE_INITIALIZED) {
|
||||
Log.e(LOG_TAG, "Audio Record can't initialize!");
|
||||
return;
|
||||
}
|
||||
|
||||
record.startRecording();
|
||||
|
||||
Log.v(LOG_TAG, "Start recording");
|
||||
|
||||
// Loop, gathering audio data and copying it to a round-robin buffer.
|
||||
while (shouldContinue) {
|
||||
int numberRead = record.read(audioBuffer, 0, audioBuffer.length);
|
||||
int maxLength = recordingBuffer.length;
|
||||
int newRecordingOffset = recordingOffset + numberRead;
|
||||
int secondCopyLength = Math.max(0, newRecordingOffset - maxLength);
|
||||
int firstCopyLength = numberRead - secondCopyLength;
|
||||
// We store off all the data for the recognition thread to access. The ML
|
||||
// thread will copy out of this buffer into its own, while holding the
|
||||
// lock, so this should be thread safe.
|
||||
recordingBufferLock.lock();
|
||||
try {
|
||||
System.arraycopy(audioBuffer, 0, recordingBuffer, recordingOffset, firstCopyLength);
|
||||
System.arraycopy(audioBuffer, firstCopyLength, recordingBuffer, 0, secondCopyLength);
|
||||
recordingOffset = newRecordingOffset % maxLength;
|
||||
} finally {
|
||||
recordingBufferLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
record.stop();
|
||||
record.release();
|
||||
}
|
||||
|
||||
public synchronized void startRecognition() {
|
||||
if (recognitionThread != null) {
|
||||
return;
|
||||
}
|
||||
shouldContinueRecognition = true;
|
||||
recognitionThread =
|
||||
new Thread(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
recognize();
|
||||
}
|
||||
});
|
||||
recognitionThread.start();
|
||||
}
|
||||
|
||||
public synchronized void stopRecognition() {
|
||||
if (recognitionThread == null) {
|
||||
return;
|
||||
}
|
||||
shouldContinueRecognition = false;
|
||||
recognitionThread = null;
|
||||
}
|
||||
|
||||
private void recognize() {
|
||||
Log.v(LOG_TAG, "Start recognition");
|
||||
|
||||
short[] inputBuffer = new short[RECORDING_LENGTH];
|
||||
float[][] floatInputBuffer = new float[RECORDING_LENGTH][1];
|
||||
float[][] outputScores = new float[1][labels.size()];
|
||||
int[] sampleRateList = new int[] {SAMPLE_RATE};
|
||||
|
||||
// Loop, grabbing recorded data and running the recognition model on it.
|
||||
while (shouldContinueRecognition) {
|
||||
// The recording thread places data in this round-robin buffer, so lock to
|
||||
// make sure there's no writing happening and then copy it to our own
|
||||
// local version.
|
||||
recordingBufferLock.lock();
|
||||
try {
|
||||
int maxLength = recordingBuffer.length;
|
||||
int firstCopyLength = maxLength - recordingOffset;
|
||||
int secondCopyLength = recordingOffset;
|
||||
System.arraycopy(recordingBuffer, recordingOffset, inputBuffer, 0, firstCopyLength);
|
||||
System.arraycopy(recordingBuffer, 0, inputBuffer, firstCopyLength, secondCopyLength);
|
||||
} finally {
|
||||
recordingBufferLock.unlock();
|
||||
}
|
||||
|
||||
// We need to feed in float values between -1.0f and 1.0f, so divide the
|
||||
// signed 16-bit inputs.
|
||||
for (int i = 0; i < RECORDING_LENGTH; ++i) {
|
||||
floatInputBuffer[i][0] = inputBuffer[i] / 32767.0f;
|
||||
}
|
||||
|
||||
Object[] inputArray = {floatInputBuffer, sampleRateList};
|
||||
Map<Integer, Object> outputMap = new HashMap<>();
|
||||
outputMap.put(0, outputScores);
|
||||
|
||||
// Run the model.
|
||||
tfLite.runForMultipleInputsOutputs(inputArray, outputMap);
|
||||
|
||||
// Use the smoother to figure out if we've had a real recognition event.
|
||||
long currentTime = System.currentTimeMillis();
|
||||
final RecognizeCommands.RecognitionResult result =
|
||||
recognizeCommands.processLatestResults(outputScores[0], currentTime);
|
||||
|
||||
runOnUiThread(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// If we do have a new command, highlight the right list entry.
|
||||
if (!result.foundCommand.startsWith("_") && result.isNewCommand) {
|
||||
int labelIndex = -1;
|
||||
for (int i = 0; i < labels.size(); ++i) {
|
||||
if (labels.get(i).equals(result.foundCommand)) {
|
||||
labelIndex = i;
|
||||
}
|
||||
}
|
||||
final View labelView = (View) labelsListView.getChildAt(labelIndex - 2);
|
||||
ValueAnimator colorAnimation =
|
||||
ValueAnimator.ofArgb(0x00b3ccff, 0xffb3ccff, 0x00b3ccff);
|
||||
colorAnimation.setDuration(750);
|
||||
colorAnimation.addUpdateListener(
|
||||
new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animator) {
|
||||
labelView.setBackgroundColor((int) animator.getAnimatedValue());
|
||||
}
|
||||
});
|
||||
colorAnimation.start();
|
||||
}
|
||||
}
|
||||
});
|
||||
try {
|
||||
// We don't need to run too frequently, so snooze for a bit.
|
||||
Thread.sleep(MINIMUM_TIME_BETWEEN_SAMPLES_MS);
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
Log.v(LOG_TAG, "End recognition");
|
||||
}
|
||||
}
|
@ -1,209 +0,0 @@
|
||||
/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
==============================================================================*/
|
||||
|
||||
package org.tensorflow.demo;
|
||||
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.content.res.AssetManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.SystemClock;
|
||||
import android.os.Trace;
|
||||
import android.util.Log;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.MappedByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Vector;
|
||||
import org.tensorflow.lite.Interpreter;
|
||||
|
||||
/** A classifier specialized to label images using TensorFlow. */
|
||||
public class TFLiteImageClassifier implements Classifier {
|
||||
private static final String TAG = "TFLiteImageClassifier";
|
||||
|
||||
// Only return this many results with at least this confidence.
|
||||
private static final int MAX_RESULTS = 3;
|
||||
|
||||
private Interpreter tfLite;
|
||||
|
||||
/** Dimensions of inputs. */
|
||||
private static final int DIM_BATCH_SIZE = 1;
|
||||
|
||||
private static final int DIM_PIXEL_SIZE = 3;
|
||||
|
||||
private static final int DIM_IMG_SIZE_X = 224;
|
||||
private static final int DIM_IMG_SIZE_Y = 224;
|
||||
|
||||
byte[][] labelProb;
|
||||
|
||||
// Pre-allocated buffers.
|
||||
private Vector<String> labels = new Vector<String>();
|
||||
private int[] intValues;
|
||||
private ByteBuffer imgData = null;
|
||||
|
||||
private TFLiteImageClassifier() {}
|
||||
|
||||
/** Memory-map the model file in Assets. */
|
||||
private static MappedByteBuffer loadModelFile(AssetManager assets, String modelFilename)
|
||||
throws IOException {
|
||||
AssetFileDescriptor fileDescriptor = assets.openFd(modelFilename);
|
||||
FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());
|
||||
FileChannel fileChannel = inputStream.getChannel();
|
||||
long startOffset = fileDescriptor.getStartOffset();
|
||||
long declaredLength = fileDescriptor.getDeclaredLength();
|
||||
return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a native TensorFlow session for classifying images.
|
||||
*
|
||||
* @param assetManager The asset manager to be used to load assets.
|
||||
* @param modelFilename The filepath of the model GraphDef protocol buffer.
|
||||
* @param labelFilename The filepath of label file for classes.
|
||||
* @param inputSize The input size. A square image of inputSize x inputSize is assumed.
|
||||
* @throws IOException
|
||||
*/
|
||||
public static Classifier create(
|
||||
AssetManager assetManager, String modelFilename, String labelFilename, int inputSize) {
|
||||
TFLiteImageClassifier c = new TFLiteImageClassifier();
|
||||
|
||||
// Read the label names into memory.
|
||||
// TODO(andrewharp): make this handle non-assets.
|
||||
Log.i(TAG, "Reading labels from: " + labelFilename);
|
||||
BufferedReader br = null;
|
||||
try {
|
||||
br = new BufferedReader(new InputStreamReader(assetManager.open(labelFilename)));
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
c.labels.add(line);
|
||||
}
|
||||
br.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Problem reading label file!" , e);
|
||||
}
|
||||
|
||||
c.imgData =
|
||||
ByteBuffer.allocateDirect(
|
||||
DIM_BATCH_SIZE * DIM_IMG_SIZE_X * DIM_IMG_SIZE_Y * DIM_PIXEL_SIZE);
|
||||
|
||||
c.imgData.order(ByteOrder.nativeOrder());
|
||||
try {
|
||||
c.tfLite = new Interpreter(loadModelFile(assetManager, modelFilename));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
// The shape of the output is [N, NUM_CLASSES], where N is the batch size.
|
||||
Log.i(TAG, "Read " + c.labels.size() + " labels");
|
||||
|
||||
// Pre-allocate buffers.
|
||||
c.intValues = new int[inputSize * inputSize];
|
||||
|
||||
c.labelProb = new byte[1][c.labels.size()];
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
/** Writes Image data into a {@code ByteBuffer}. */
|
||||
private void convertBitmapToByteBuffer(Bitmap bitmap) {
|
||||
if (imgData == null) {
|
||||
return;
|
||||
}
|
||||
imgData.rewind();
|
||||
bitmap.getPixels(intValues, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
|
||||
// Convert the image to floating point.
|
||||
int pixel = 0;
|
||||
long startTime = SystemClock.uptimeMillis();
|
||||
for (int i = 0; i < DIM_IMG_SIZE_X; ++i) {
|
||||
for (int j = 0; j < DIM_IMG_SIZE_Y; ++j) {
|
||||
final int val = intValues[pixel++];
|
||||
imgData.put((byte) ((val >> 16) & 0xFF));
|
||||
imgData.put((byte) ((val >> 8) & 0xFF));
|
||||
imgData.put((byte) (val & 0xFF));
|
||||
}
|
||||
}
|
||||
long endTime = SystemClock.uptimeMillis();
|
||||
Log.d(TAG, "Timecost to put values into ByteBuffer: " + Long.toString(endTime - startTime));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Recognition> recognizeImage(final Bitmap bitmap) {
|
||||
// Log this method so that it can be analyzed with systrace.
|
||||
Trace.beginSection("recognizeImage");
|
||||
|
||||
Trace.beginSection("preprocessBitmap");
|
||||
|
||||
long startTime;
|
||||
long endTime;
|
||||
startTime = SystemClock.uptimeMillis();
|
||||
|
||||
convertBitmapToByteBuffer(bitmap);
|
||||
|
||||
// Run the inference call.
|
||||
Trace.beginSection("run");
|
||||
startTime = SystemClock.uptimeMillis();
|
||||
tfLite.run(imgData, labelProb);
|
||||
endTime = SystemClock.uptimeMillis();
|
||||
Log.i(TAG, "Inf time: " + (endTime - startTime));
|
||||
Trace.endSection();
|
||||
|
||||
// Find the best classifications.
|
||||
PriorityQueue<Recognition> pq =
|
||||
new PriorityQueue<Recognition>(
|
||||
3,
|
||||
new Comparator<Recognition>() {
|
||||
@Override
|
||||
public int compare(Recognition lhs, Recognition rhs) {
|
||||
// Intentionally reversed to put high confidence at the head of the queue.
|
||||
return Float.compare(rhs.getConfidence(), lhs.getConfidence());
|
||||
}
|
||||
});
|
||||
for (int i = 0; i < labels.size(); ++i) {
|
||||
pq.add(
|
||||
new Recognition(
|
||||
"" + i,
|
||||
labels.size() > i ? labels.get(i) : "unknown",
|
||||
(float) labelProb[0][i],
|
||||
null));
|
||||
}
|
||||
final ArrayList<Recognition> recognitions = new ArrayList<Recognition>();
|
||||
int recognitionsSize = Math.min(pq.size(), MAX_RESULTS);
|
||||
for (int i = 0; i < recognitionsSize; ++i) {
|
||||
recognitions.add(pq.poll());
|
||||
}
|
||||
Trace.endSection(); // "recognizeImage"
|
||||
return recognitions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableStatLogging(boolean logStats) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatString() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
@ -1,233 +0,0 @@
|
||||
/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
==============================================================================*/
|
||||
|
||||
package org.tensorflow.demo;
|
||||
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.content.res.AssetManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Trace;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.MappedByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Vector;
|
||||
import org.tensorflow.demo.env.Logger;
|
||||
import org.tensorflow.lite.Interpreter;
|
||||
|
||||
/**
|
||||
* Wrapper for frozen detection models trained using the Tensorflow Object Detection API:
|
||||
* github.com/tensorflow/models/tree/master/research/object_detection
|
||||
*/
|
||||
public class TFLiteObjectDetectionAPIModel implements Classifier {
|
||||
private static final Logger LOGGER = new Logger();
|
||||
|
||||
// Only return this many results.
|
||||
private static final int NUM_DETECTIONS = 10;
|
||||
private boolean isModelQuantized;
|
||||
// Float model
|
||||
private static final float IMAGE_MEAN = 128.0f;
|
||||
private static final float IMAGE_STD = 128.0f;
|
||||
// Number of threads in the java app
|
||||
private static final int NUM_THREADS = 4;
|
||||
// Config values.
|
||||
private int inputSize;
|
||||
// Pre-allocated buffers.
|
||||
private Vector<String> labels = new Vector<String>();
|
||||
private int[] intValues;
|
||||
// outputLocations: array of shape [Batchsize, NUM_DETECTIONS,4]
|
||||
// contains the location of detected boxes
|
||||
private float[][][] outputLocations;
|
||||
// outputClasses: array of shape [Batchsize, NUM_DETECTIONS]
|
||||
// contains the classes of detected boxes
|
||||
private float[][] outputClasses;
|
||||
// outputScores: array of shape [Batchsize, NUM_DETECTIONS]
|
||||
// contains the scores of detected boxes
|
||||
private float[][] outputScores;
|
||||
// numDetections: array of shape [Batchsize]
|
||||
// contains the number of detected boxes
|
||||
private float[] numDetections;
|
||||
|
||||
private ByteBuffer imgData;
|
||||
|
||||
private Interpreter tfLite;
|
||||
|
||||
|
||||
/** Memory-map the model file in Assets. */
|
||||
private static MappedByteBuffer loadModelFile(AssetManager assets, String modelFilename)
|
||||
throws IOException {
|
||||
AssetFileDescriptor fileDescriptor = assets.openFd(modelFilename);
|
||||
FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());
|
||||
FileChannel fileChannel = inputStream.getChannel();
|
||||
long startOffset = fileDescriptor.getStartOffset();
|
||||
long declaredLength = fileDescriptor.getDeclaredLength();
|
||||
return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a native TensorFlow session for classifying images.
|
||||
*
|
||||
* @param assetManager The asset manager to be used to load assets.
|
||||
* @param modelFilename The filepath of the model GraphDef protocol buffer.
|
||||
* @param labelFilename The filepath of label file for classes.
|
||||
* @param inputSize The size of image input
|
||||
* @param isQuantized Boolean representing model is quantized or not
|
||||
*/
|
||||
public static Classifier create(
|
||||
final AssetManager assetManager,
|
||||
final String modelFilename,
|
||||
final String labelFilename,
|
||||
final int inputSize,
|
||||
final boolean isQuantized)
|
||||
throws IOException {
|
||||
final TFLiteObjectDetectionAPIModel d = new TFLiteObjectDetectionAPIModel();
|
||||
|
||||
InputStream labelsInput = null;
|
||||
labelsInput = assetManager.open(labelFilename);
|
||||
BufferedReader br = null;
|
||||
br = new BufferedReader(new InputStreamReader(labelsInput));
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
LOGGER.w(line);
|
||||
d.labels.add(line);
|
||||
}
|
||||
br.close();
|
||||
|
||||
d.inputSize = inputSize;
|
||||
|
||||
try {
|
||||
d.tfLite = new Interpreter(loadModelFile(assetManager, modelFilename));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
d.isModelQuantized = isQuantized;
|
||||
// Pre-allocate buffers.
|
||||
int numBytesPerChannel;
|
||||
if (isQuantized) {
|
||||
numBytesPerChannel = 1; // Quantized
|
||||
} else {
|
||||
numBytesPerChannel = 4; // Floating point
|
||||
}
|
||||
d.imgData = ByteBuffer.allocateDirect(1 * d.inputSize * d.inputSize * 3 * numBytesPerChannel);
|
||||
d.imgData.order(ByteOrder.nativeOrder());
|
||||
d.intValues = new int[d.inputSize * d.inputSize];
|
||||
|
||||
d.tfLite.setNumThreads(NUM_THREADS);
|
||||
d.outputLocations = new float[1][NUM_DETECTIONS][4];
|
||||
d.outputClasses = new float[1][NUM_DETECTIONS];
|
||||
d.outputScores = new float[1][NUM_DETECTIONS];
|
||||
d.numDetections = new float[1];
|
||||
return d;
|
||||
}
|
||||
|
||||
private TFLiteObjectDetectionAPIModel() {}
|
||||
|
||||
@Override
|
||||
public List<Recognition> recognizeImage(final Bitmap bitmap) {
|
||||
// Log this method so that it can be analyzed with systrace.
|
||||
Trace.beginSection("recognizeImage");
|
||||
|
||||
Trace.beginSection("preprocessBitmap");
|
||||
// Preprocess the image data from 0-255 int to normalized float based
|
||||
// on the provided parameters.
|
||||
bitmap.getPixels(intValues, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
|
||||
|
||||
imgData.rewind();
|
||||
for (int i = 0; i < inputSize; ++i) {
|
||||
for (int j = 0; j < inputSize; ++j) {
|
||||
int pixelValue = intValues[i * inputSize + j];
|
||||
if (isModelQuantized) {
|
||||
// Quantized model
|
||||
imgData.put((byte) ((pixelValue >> 16) & 0xFF));
|
||||
imgData.put((byte) ((pixelValue >> 8) & 0xFF));
|
||||
imgData.put((byte) (pixelValue & 0xFF));
|
||||
} else { // Float model
|
||||
imgData.putFloat((((pixelValue >> 16) & 0xFF) - IMAGE_MEAN) / IMAGE_STD);
|
||||
imgData.putFloat((((pixelValue >> 8) & 0xFF) - IMAGE_MEAN) / IMAGE_STD);
|
||||
imgData.putFloat(((pixelValue & 0xFF) - IMAGE_MEAN) / IMAGE_STD);
|
||||
}
|
||||
}
|
||||
}
|
||||
Trace.endSection(); // preprocessBitmap
|
||||
|
||||
// Copy the input data into TensorFlow.
|
||||
Trace.beginSection("feed");
|
||||
outputLocations = new float[1][NUM_DETECTIONS][4];
|
||||
outputClasses = new float[1][NUM_DETECTIONS];
|
||||
outputScores = new float[1][NUM_DETECTIONS];
|
||||
numDetections = new float[1];
|
||||
|
||||
Object[] inputArray = {imgData};
|
||||
Map<Integer, Object> outputMap = new HashMap<>();
|
||||
outputMap.put(0, outputLocations);
|
||||
outputMap.put(1, outputClasses);
|
||||
outputMap.put(2, outputScores);
|
||||
outputMap.put(3, numDetections);
|
||||
Trace.endSection();
|
||||
|
||||
// Run the inference call.
|
||||
Trace.beginSection("run");
|
||||
tfLite.runForMultipleInputsOutputs(inputArray, outputMap);
|
||||
Trace.endSection();
|
||||
|
||||
// Show the best detections.
|
||||
// after scaling them back to the input size.
|
||||
final ArrayList<Recognition> recognitions = new ArrayList<>(NUM_DETECTIONS);
|
||||
for (int i = 0; i < NUM_DETECTIONS; ++i) {
|
||||
final RectF detection =
|
||||
new RectF(
|
||||
outputLocations[0][i][1] * inputSize,
|
||||
outputLocations[0][i][0] * inputSize,
|
||||
outputLocations[0][i][3] * inputSize,
|
||||
outputLocations[0][i][2] * inputSize);
|
||||
// SSD Mobilenet V1 Model assumes class 0 is background class
|
||||
// in label file and class labels start from 1 to number_of_classes+1,
|
||||
// while outputClasses correspond to class index from 0 to number_of_classes
|
||||
int labelOffset = 1;
|
||||
recognitions.add(
|
||||
new Recognition(
|
||||
"" + i,
|
||||
labels.get((int) outputClasses[0][i] + labelOffset),
|
||||
outputScores[0][i],
|
||||
detection));
|
||||
}
|
||||
Trace.endSection(); // "recognizeImage"
|
||||
return recognitions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableStatLogging(final boolean logStats) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatString() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
/* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
==============================================================================*/
|
||||
|
||||
package org.tensorflow.demo.env;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.util.Log;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/** Utilities for dealing with assets. */
|
||||
public class AssetUtils {
|
||||
|
||||
private static final String TAG = AssetUtils.class.getSimpleName();
|
||||
|
||||
private static final int BYTE_BUF_SIZE = 2048;
|
||||
|
||||
/**
|
||||
* Copies a file from assets.
|
||||
*
|
||||
* @param context application context used to discover assets.
|
||||
* @param assetName the relative file name within assets.
|
||||
* @param targetName the target file name, always over write the existing file.
|
||||
* @throws IOException if operation fails.
|
||||
*/
|
||||
public static void copy(Context context, String assetName, String targetName) throws IOException {
|
||||
|
||||
Log.d(TAG, "creating file " + targetName + " from " + assetName);
|
||||
|
||||
File targetFile = null;
|
||||
InputStream inputStream = null;
|
||||
FileOutputStream outputStream = null;
|
||||
|
||||
try {
|
||||
AssetManager assets = context.getAssets();
|
||||
targetFile = new File(targetName);
|
||||
inputStream = assets.open(assetName);
|
||||
// TODO(kanlig): refactor log messages to make them more useful.
|
||||
Log.d(TAG, "Creating outputstream");
|
||||
outputStream = new FileOutputStream(targetFile, false /* append */);
|
||||
copy(inputStream, outputStream);
|
||||
} finally {
|
||||
if (outputStream != null) {
|
||||
outputStream.close();
|
||||
}
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void copy(InputStream from, OutputStream to) throws IOException {
|
||||
byte[] buf = new byte[BYTE_BUF_SIZE];
|
||||
while (true) {
|
||||
int r = from.read(buf);
|
||||
if (r == -1) {
|
||||
break;
|
||||
}
|
||||
to.write(buf, 0, r);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
==============================================================================*/
|
||||
|
||||
package org.tensorflow.demo.env;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Paint.Align;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* A class that encapsulates the tedious bits of rendering legible, bordered text onto a canvas.
|
||||
*/
|
||||
public class BorderedText {
|
||||
private final Paint interiorPaint;
|
||||
private final Paint exteriorPaint;
|
||||
|
||||
private final float textSize;
|
||||
|
||||
/**
|
||||
* Creates a left-aligned bordered text object with a white interior, and a black exterior with
|
||||
* the specified text size.
|
||||
*
|
||||
* @param textSize text size in pixels
|
||||
*/
|
||||
public BorderedText(final float textSize) {
|
||||
this(Color.WHITE, Color.BLACK, textSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a bordered text object with the specified interior and exterior colors, text size and
|
||||
* alignment.
|
||||
*
|
||||
* @param interiorColor the interior text color
|
||||
* @param exteriorColor the exterior text color
|
||||
* @param textSize text size in pixels
|
||||
*/
|
||||
public BorderedText(final int interiorColor, final int exteriorColor, final float textSize) {
|
||||
interiorPaint = new Paint();
|
||||
interiorPaint.setTextSize(textSize);
|
||||
interiorPaint.setColor(interiorColor);
|
||||
interiorPaint.setStyle(Style.FILL);
|
||||
interiorPaint.setAntiAlias(false);
|
||||
interiorPaint.setAlpha(255);
|
||||
|
||||
exteriorPaint = new Paint();
|
||||
exteriorPaint.setTextSize(textSize);
|
||||
exteriorPaint.setColor(exteriorColor);
|
||||
exteriorPaint.setStyle(Style.FILL_AND_STROKE);
|
||||
exteriorPaint.setStrokeWidth(textSize / 8);
|
||||
exteriorPaint.setAntiAlias(false);
|
||||
exteriorPaint.setAlpha(255);
|
||||
|
||||
this.textSize = textSize;
|
||||
}
|
||||
|
||||
public void setTypeface(Typeface typeface) {
|
||||
interiorPaint.setTypeface(typeface);
|
||||
exteriorPaint.setTypeface(typeface);
|
||||
}
|
||||
|
||||
public void drawText(final Canvas canvas, final float posX, final float posY, final String text) {
|
||||
canvas.drawText(text, posX, posY, exteriorPaint);
|
||||
canvas.drawText(text, posX, posY, interiorPaint);
|
||||
}
|
||||
|
||||
public void drawLines(Canvas canvas, final float posX, final float posY, Vector<String> lines) {
|
||||
int lineNum = 0;
|
||||
for (final String line : lines) {
|
||||
drawText(canvas, posX, posY - getTextSize() * (lines.size() - lineNum - 1), line);
|
||||
++lineNum;
|
||||
}
|
||||
}
|
||||
|
||||
public void setInteriorColor(final int color) {
|
||||
interiorPaint.setColor(color);
|
||||
}
|
||||
|
||||
public void setExteriorColor(final int color) {
|
||||
exteriorPaint.setColor(color);
|
||||
}
|
||||
|
||||
public float getTextSize() {
|
||||
return textSize;
|
||||
}
|
||||
|
||||
public void setAlpha(final int alpha) {
|
||||
interiorPaint.setAlpha(alpha);
|
||||
exteriorPaint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
public void getTextBounds(
|
||||
final String line, final int index, final int count, final Rect lineBounds) {
|
||||
interiorPaint.getTextBounds(line, index, count, lineBounds);
|
||||
}
|
||||
|
||||
public void setTextAlign(final Align align) {
|
||||
interiorPaint.setTextAlign(align);
|
||||
exteriorPaint.setTextAlign(align);
|
||||
}
|
||||
}
|
@ -1,344 +0,0 @@
|
||||
/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
==============================================================================*/
|
||||
|
||||
package org.tensorflow.demo.env;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Matrix;
|
||||
import android.os.Environment;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
/**
|
||||
* Utility class for manipulating images.
|
||||
**/
|
||||
public class ImageUtils {
|
||||
@SuppressWarnings("unused")
|
||||
private static final Logger LOGGER = new Logger();
|
||||
|
||||
static {
|
||||
try {
|
||||
System.loadLibrary("tensorflow_demo");
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
LOGGER.w("Native library not found, native RGB -> YUV conversion may be unavailable.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to compute the allocated size in bytes of a YUV420SP image
|
||||
* of the given dimensions.
|
||||
*/
|
||||
public static int getYUVByteSize(final int width, final int height) {
|
||||
// The luminance plane requires 1 byte per pixel.
|
||||
final int ySize = width * height;
|
||||
|
||||
// The UV plane works on 2x2 blocks, so dimensions with odd size must be rounded up.
|
||||
// Each 2x2 block takes 2 bytes to encode, one each for U and V.
|
||||
final int uvSize = ((width + 1) / 2) * ((height + 1) / 2) * 2;
|
||||
|
||||
return ySize + uvSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a Bitmap object to disk for analysis.
|
||||
*
|
||||
* @param bitmap The bitmap to save.
|
||||
*/
|
||||
public static void saveBitmap(final Bitmap bitmap) {
|
||||
saveBitmap(bitmap, "preview.png");
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a Bitmap object to disk for analysis.
|
||||
*
|
||||
* @param bitmap The bitmap to save.
|
||||
* @param filename The location to save the bitmap to.
|
||||
*/
|
||||
public static void saveBitmap(final Bitmap bitmap, final String filename) {
|
||||
final String root =
|
||||
Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "tensorflow";
|
||||
LOGGER.i("Saving %dx%d bitmap to %s.", bitmap.getWidth(), bitmap.getHeight(), root);
|
||||
final File myDir = new File(root);
|
||||
|
||||
if (!myDir.mkdirs()) {
|
||||
LOGGER.i("Make dir failed");
|
||||
}
|
||||
|
||||
final String fname = filename;
|
||||
final File file = new File(myDir, fname);
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
try {
|
||||
final FileOutputStream out = new FileOutputStream(file);
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 99, out);
|
||||
out.flush();
|
||||
out.close();
|
||||
} catch (final Exception e) {
|
||||
LOGGER.e(e, "Exception!");
|
||||
}
|
||||
}
|
||||
|
||||
// This value is 2 ^ 18 - 1, and is used to clamp the RGB values before their ranges
|
||||
// are normalized to eight bits.
|
||||
static final int kMaxChannelValue = 262143;
|
||||
|
||||
// Always prefer the native implementation if available.
|
||||
private static boolean useNativeConversion = false;
|
||||
|
||||
public static void convertYUV420SPToARGB8888(
|
||||
byte[] input,
|
||||
int width,
|
||||
int height,
|
||||
int[] output) {
|
||||
if (useNativeConversion) {
|
||||
try {
|
||||
ImageUtils.convertYUV420SPToARGB8888(input, output, width, height, false);
|
||||
return;
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
LOGGER.w(
|
||||
"Native YUV420SP -> RGB implementation not found, falling back to Java implementation");
|
||||
useNativeConversion = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Java implementation of YUV420SP to ARGB8888 converting
|
||||
final int frameSize = width * height;
|
||||
for (int j = 0, yp = 0; j < height; j++) {
|
||||
int uvp = frameSize + (j >> 1) * width;
|
||||
int u = 0;
|
||||
int v = 0;
|
||||
|
||||
for (int i = 0; i < width; i++, yp++) {
|
||||
int y = 0xff & input[yp];
|
||||
if ((i & 1) == 0) {
|
||||
v = 0xff & input[uvp++];
|
||||
u = 0xff & input[uvp++];
|
||||
}
|
||||
|
||||
output[yp] = YUV2RGB(y, u, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int YUV2RGB(int y, int u, int v) {
|
||||
// Adjust and check YUV values
|
||||
y = (y - 16) < 0 ? 0 : (y - 16);
|
||||
u -= 128;
|
||||
v -= 128;
|
||||
|
||||
// This is the floating point equivalent. We do the conversion in integer
|
||||
// because some Android devices do not have floating point in hardware.
|
||||
// nR = (int)(1.164 * nY + 2.018 * nU);
|
||||
// nG = (int)(1.164 * nY - 0.813 * nV - 0.391 * nU);
|
||||
// nB = (int)(1.164 * nY + 1.596 * nV);
|
||||
int y1192 = 1192 * y;
|
||||
int r = (y1192 + 1634 * v);
|
||||
int g = (y1192 - 833 * v - 400 * u);
|
||||
int b = (y1192 + 2066 * u);
|
||||
|
||||
// Clipping RGB values to be inside boundaries [ 0 , kMaxChannelValue ]
|
||||
r = r > kMaxChannelValue ? kMaxChannelValue : (r < 0 ? 0 : r);
|
||||
g = g > kMaxChannelValue ? kMaxChannelValue : (g < 0 ? 0 : g);
|
||||
b = b > kMaxChannelValue ? kMaxChannelValue : (b < 0 ? 0 : b);
|
||||
|
||||
return 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
|
||||
}
|
||||
|
||||
|
||||
public static void convertYUV420ToARGB8888(
|
||||
byte[] yData,
|
||||
byte[] uData,
|
||||
byte[] vData,
|
||||
int width,
|
||||
int height,
|
||||
int yRowStride,
|
||||
int uvRowStride,
|
||||
int uvPixelStride,
|
||||
int[] out) {
|
||||
if (useNativeConversion) {
|
||||
try {
|
||||
convertYUV420ToARGB8888(
|
||||
yData, uData, vData, out, width, height, yRowStride, uvRowStride, uvPixelStride, false);
|
||||
return;
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
LOGGER.w(
|
||||
"Native YUV420 -> RGB implementation not found, falling back to Java implementation");
|
||||
useNativeConversion = false;
|
||||
}
|
||||
}
|
||||
|
||||
int yp = 0;
|
||||
for (int j = 0; j < height; j++) {
|
||||
int pY = yRowStride * j;
|
||||
int pUV = uvRowStride * (j >> 1);
|
||||
|
||||
for (int i = 0; i < width; i++) {
|
||||
int uv_offset = pUV + (i >> 1) * uvPixelStride;
|
||||
|
||||
out[yp++] = YUV2RGB(
|
||||
0xff & yData[pY + i],
|
||||
0xff & uData[uv_offset],
|
||||
0xff & vData[uv_offset]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts YUV420 semi-planar data to ARGB 8888 data using the supplied width and height. The
|
||||
* input and output must already be allocated and non-null. For efficiency, no error checking is
|
||||
* performed.
|
||||
*
|
||||
* @param input The array of YUV 4:2:0 input data.
|
||||
* @param output A pre-allocated array for the ARGB 8:8:8:8 output data.
|
||||
* @param width The width of the input image.
|
||||
* @param height The height of the input image.
|
||||
* @param halfSize If true, downsample to 50% in each dimension, otherwise not.
|
||||
*/
|
||||
private static native void convertYUV420SPToARGB8888(
|
||||
byte[] input, int[] output, int width, int height, boolean halfSize);
|
||||
|
||||
/**
|
||||
* Converts YUV420 semi-planar data to ARGB 8888 data using the supplied width
|
||||
* and height. The input and output must already be allocated and non-null.
|
||||
* For efficiency, no error checking is performed.
|
||||
*
|
||||
* @param y
|
||||
* @param u
|
||||
* @param v
|
||||
* @param uvPixelStride
|
||||
* @param width The width of the input image.
|
||||
* @param height The height of the input image.
|
||||
* @param halfSize If true, downsample to 50% in each dimension, otherwise not.
|
||||
* @param output A pre-allocated array for the ARGB 8:8:8:8 output data.
|
||||
*/
|
||||
private static native void convertYUV420ToARGB8888(
|
||||
byte[] y,
|
||||
byte[] u,
|
||||
byte[] v,
|
||||
int[] output,
|
||||
int width,
|
||||
int height,
|
||||
int yRowStride,
|
||||
int uvRowStride,
|
||||
int uvPixelStride,
|
||||
boolean halfSize);
|
||||
|
||||
/**
|
||||
* Converts YUV420 semi-planar data to RGB 565 data using the supplied width
|
||||
* and height. The input and output must already be allocated and non-null.
|
||||
* For efficiency, no error checking is performed.
|
||||
*
|
||||
* @param input The array of YUV 4:2:0 input data.
|
||||
* @param output A pre-allocated array for the RGB 5:6:5 output data.
|
||||
* @param width The width of the input image.
|
||||
* @param height The height of the input image.
|
||||
*/
|
||||
private static native void convertYUV420SPToRGB565(
|
||||
byte[] input, byte[] output, int width, int height);
|
||||
|
||||
/**
|
||||
* Converts 32-bit ARGB8888 image data to YUV420SP data. This is useful, for
|
||||
* instance, in creating data to feed the classes that rely on raw camera
|
||||
* preview frames.
|
||||
*
|
||||
* @param input An array of input pixels in ARGB8888 format.
|
||||
* @param output A pre-allocated array for the YUV420SP output data.
|
||||
* @param width The width of the input image.
|
||||
* @param height The height of the input image.
|
||||
*/
|
||||
private static native void convertARGB8888ToYUV420SP(
|
||||
int[] input, byte[] output, int width, int height);
|
||||
|
||||
/**
|
||||
* Converts 16-bit RGB565 image data to YUV420SP data. This is useful, for
|
||||
* instance, in creating data to feed the classes that rely on raw camera
|
||||
* preview frames.
|
||||
*
|
||||
* @param input An array of input pixels in RGB565 format.
|
||||
* @param output A pre-allocated array for the YUV420SP output data.
|
||||
* @param width The width of the input image.
|
||||
* @param height The height of the input image.
|
||||
*/
|
||||
private static native void convertRGB565ToYUV420SP(
|
||||
byte[] input, byte[] output, int width, int height);
|
||||
|
||||
/**
|
||||
* Returns a transformation matrix from one reference frame into another.
|
||||
* Handles cropping (if maintaining aspect ratio is desired) and rotation.
|
||||
*
|
||||
* @param srcWidth Width of source frame.
|
||||
* @param srcHeight Height of source frame.
|
||||
* @param dstWidth Width of destination frame.
|
||||
* @param dstHeight Height of destination frame.
|
||||
* @param applyRotation Amount of rotation to apply from one frame to another.
|
||||
* Must be a multiple of 90.
|
||||
* @param maintainAspectRatio If true, will ensure that scaling in x and y remains constant,
|
||||
* cropping the image if necessary.
|
||||
* @return The transformation fulfilling the desired requirements.
|
||||
*/
|
||||
public static Matrix getTransformationMatrix(
|
||||
final int srcWidth,
|
||||
final int srcHeight,
|
||||
final int dstWidth,
|
||||
final int dstHeight,
|
||||
final int applyRotation,
|
||||
final boolean maintainAspectRatio) {
|
||||
final Matrix matrix = new Matrix();
|
||||
|
||||
if (applyRotation != 0) {
|
||||
if (applyRotation % 90 != 0) {
|
||||
LOGGER.w("Rotation of %d % 90 != 0", applyRotation);
|
||||
}
|
||||
|
||||
// Translate so center of image is at origin.
|
||||
matrix.postTranslate(-srcWidth / 2.0f, -srcHeight / 2.0f);
|
||||
|
||||
// Rotate around origin.
|
||||
matrix.postRotate(applyRotation);
|
||||
}
|
||||
|
||||
// Account for the already applied rotation, if any, and then determine how
|
||||
// much scaling is needed for each axis.
|
||||
final boolean transpose = (Math.abs(applyRotation) + 90) % 180 == 0;
|
||||
|
||||
final int inWidth = transpose ? srcHeight : srcWidth;
|
||||
final int inHeight = transpose ? srcWidth : srcHeight;
|
||||
|
||||
// Apply scaling if necessary.
|
||||
if (inWidth != dstWidth || inHeight != dstHeight) {
|
||||
final float scaleFactorX = dstWidth / (float) inWidth;
|
||||
final float scaleFactorY = dstHeight / (float) inHeight;
|
||||
|
||||
if (maintainAspectRatio) {
|
||||
// Scale by minimum factor so that dst is filled completely while
|
||||
// maintaining the aspect ratio. Some image may fall off the edge.
|
||||
final float scaleFactor = Math.max(scaleFactorX, scaleFactorY);
|
||||
matrix.postScale(scaleFactor, scaleFactor);
|
||||
} else {
|
||||
// Scale exactly to fill dst from src.
|
||||
matrix.postScale(scaleFactorX, scaleFactorY);
|
||||
}
|
||||
}
|
||||
|
||||
if (applyRotation != 0) {
|
||||
// Translate back from origin centered reference to destination frame.
|
||||
matrix.postTranslate(dstWidth / 2.0f, dstHeight / 2.0f);
|
||||
}
|
||||
|
||||
return matrix;
|
||||
}
|
||||
}
|
@ -1,190 +0,0 @@
|
||||
/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
==============================================================================*/
|
||||
|
||||
package org.tensorflow.demo.env;
|
||||
|
||||
import android.util.Log;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Wrapper for the platform log function, allows convenient message prefixing and log disabling.
|
||||
*/
|
||||
public final class Logger {
|
||||
private static final String DEFAULT_TAG = "tensorflow";
|
||||
private static final int DEFAULT_MIN_LOG_LEVEL = Log.DEBUG;
|
||||
|
||||
// Classes to be ignored when examining the stack trace
|
||||
private static final Set<String> IGNORED_CLASS_NAMES;
|
||||
|
||||
static {
|
||||
IGNORED_CLASS_NAMES = new HashSet<String>(3);
|
||||
IGNORED_CLASS_NAMES.add("dalvik.system.VMStack");
|
||||
IGNORED_CLASS_NAMES.add("java.lang.Thread");
|
||||
IGNORED_CLASS_NAMES.add(Logger.class.getCanonicalName());
|
||||
}
|
||||
|
||||
private final String tag;
|
||||
private final String messagePrefix;
|
||||
private int minLogLevel = DEFAULT_MIN_LOG_LEVEL;
|
||||
|
||||
/**
|
||||
* Creates a Logger using the class name as the message prefix.
|
||||
*
|
||||
* @param clazz the simple name of this class is used as the message prefix.
|
||||
*/
|
||||
public Logger(final Class<?> clazz) {
|
||||
this(clazz.getSimpleName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Logger using the specified message prefix.
|
||||
*
|
||||
* @param messagePrefix is prepended to the text of every message.
|
||||
*/
|
||||
public Logger(final String messagePrefix) {
|
||||
this(DEFAULT_TAG, messagePrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Logger with a custom tag and a custom message prefix. If the message prefix
|
||||
* is set to <pre>null</pre>, the caller's class name is used as the prefix.
|
||||
*
|
||||
* @param tag identifies the source of a log message.
|
||||
* @param messagePrefix prepended to every message if non-null. If null, the name of the caller is
|
||||
* being used
|
||||
*/
|
||||
public Logger(final String tag, final String messagePrefix) {
|
||||
this.tag = tag;
|
||||
final String prefix = messagePrefix == null ? getCallerSimpleName() : messagePrefix;
|
||||
this.messagePrefix = (prefix.length() > 0) ? prefix + ": " : prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Logger using the caller's class name as the message prefix.
|
||||
*/
|
||||
public Logger() {
|
||||
this(DEFAULT_TAG, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Logger using the caller's class name as the message prefix.
|
||||
*/
|
||||
public Logger(final int minLogLevel) {
|
||||
this(DEFAULT_TAG, null);
|
||||
this.minLogLevel = minLogLevel;
|
||||
}
|
||||
|
||||
public void setMinLogLevel(final int minLogLevel) {
|
||||
this.minLogLevel = minLogLevel;
|
||||
}
|
||||
|
||||
public boolean isLoggable(final int logLevel) {
|
||||
return logLevel >= minLogLevel || Log.isLoggable(tag, logLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return caller's simple name.
|
||||
*
|
||||
* Android getStackTrace() returns an array that looks like this:
|
||||
* stackTrace[0]: dalvik.system.VMStack
|
||||
* stackTrace[1]: java.lang.Thread
|
||||
* stackTrace[2]: com.google.android.apps.unveil.env.UnveilLogger
|
||||
* stackTrace[3]: com.google.android.apps.unveil.BaseApplication
|
||||
*
|
||||
* This function returns the simple version of the first non-filtered name.
|
||||
*
|
||||
* @return caller's simple name
|
||||
*/
|
||||
private static String getCallerSimpleName() {
|
||||
// Get the current callstack so we can pull the class of the caller off of it.
|
||||
final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
|
||||
for (final StackTraceElement elem : stackTrace) {
|
||||
final String className = elem.getClassName();
|
||||
if (!IGNORED_CLASS_NAMES.contains(className)) {
|
||||
// We're only interested in the simple name of the class, not the complete package.
|
||||
final String[] classParts = className.split("\\.");
|
||||
return classParts[classParts.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
return Logger.class.getSimpleName();
|
||||
}
|
||||
|
||||
private String toMessage(final String format, final Object... args) {
|
||||
return messagePrefix + (args.length > 0 ? String.format(format, args) : format);
|
||||
}
|
||||
|
||||
public void v(final String format, final Object... args) {
|
||||
if (isLoggable(Log.VERBOSE)) {
|
||||
Log.v(tag, toMessage(format, args));
|
||||
}
|
||||
}
|
||||
|
||||
public void v(final Throwable t, final String format, final Object... args) {
|
||||
if (isLoggable(Log.VERBOSE)) {
|
||||
Log.v(tag, toMessage(format, args), t);
|
||||
}
|
||||
}
|
||||
|
||||
public void d(final String format, final Object... args) {
|
||||
if (isLoggable(Log.DEBUG)) {
|
||||
Log.d(tag, toMessage(format, args));
|
||||
}
|
||||
}
|
||||
|
||||
public void d(final Throwable t, final String format, final Object... args) {
|
||||
if (isLoggable(Log.DEBUG)) {
|
||||
Log.d(tag, toMessage(format, args), t);
|
||||
}
|
||||
}
|
||||
|
||||
public void i(final String format, final Object... args) {
|
||||
if (isLoggable(Log.INFO)) {
|
||||
Log.i(tag, toMessage(format, args));
|
||||
}
|
||||
}
|
||||
|
||||
public void i(final Throwable t, final String format, final Object... args) {
|
||||
if (isLoggable(Log.INFO)) {
|
||||
Log.i(tag, toMessage(format, args), t);
|
||||
}
|
||||
}
|
||||
|
||||
public void w(final String format, final Object... args) {
|
||||
if (isLoggable(Log.WARN)) {
|
||||
Log.w(tag, toMessage(format, args));
|
||||
}
|
||||
}
|
||||
|
||||
public void w(final Throwable t, final String format, final Object... args) {
|
||||
if (isLoggable(Log.WARN)) {
|
||||
Log.w(tag, toMessage(format, args), t);
|
||||
}
|
||||
}
|
||||
|
||||
public void e(final String format, final Object... args) {
|
||||
if (isLoggable(Log.ERROR)) {
|
||||
Log.e(tag, toMessage(format, args));
|
||||
}
|
||||
}
|
||||
|
||||
public void e(final Throwable t, final String format, final Object... args) {
|
||||
if (isLoggable(Log.ERROR)) {
|
||||
Log.e(tag, toMessage(format, args), t);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,143 +0,0 @@
|
||||
/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
==============================================================================*/
|
||||
|
||||
package org.tensorflow.demo.env;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.text.TextUtils;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Size class independent of a Camera object.
|
||||
*/
|
||||
public class Size implements Comparable<Size>, Serializable {
|
||||
|
||||
// 1.4 went out with this UID so we'll need to maintain it to preserve pending queries when
|
||||
// upgrading.
|
||||
public static final long serialVersionUID = 7689808733290872361L;
|
||||
|
||||
public final int width;
|
||||
public final int height;
|
||||
|
||||
public Size(final int width, final int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public Size(final Bitmap bmp) {
|
||||
this.width = bmp.getWidth();
|
||||
this.height = bmp.getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate a size by the given number of degrees.
|
||||
* @param size Size to rotate.
|
||||
* @param rotation Degrees {0, 90, 180, 270} to rotate the size.
|
||||
* @return Rotated size.
|
||||
*/
|
||||
public static Size getRotatedSize(final Size size, final int rotation) {
|
||||
if (rotation % 180 != 0) {
|
||||
// The phone is portrait, therefore the camera is sideways and frame should be rotated.
|
||||
return new Size(size.height, size.width);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
public static Size parseFromString(String sizeString) {
|
||||
if (TextUtils.isEmpty(sizeString)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
sizeString = sizeString.trim();
|
||||
|
||||
// The expected format is "<width>x<height>".
|
||||
final String[] components = sizeString.split("x");
|
||||
if (components.length == 2) {
|
||||
try {
|
||||
final int width = Integer.parseInt(components[0]);
|
||||
final int height = Integer.parseInt(components[1]);
|
||||
return new Size(width, height);
|
||||
} catch (final NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Size> sizeStringToList(final String sizes) {
|
||||
final List<Size> sizeList = new ArrayList<Size>();
|
||||
if (sizes != null) {
|
||||
final String[] pairs = sizes.split(",");
|
||||
for (final String pair : pairs) {
|
||||
final Size size = Size.parseFromString(pair);
|
||||
if (size != null) {
|
||||
sizeList.add(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sizeList;
|
||||
}
|
||||
|
||||
public static String sizeListToString(final List<Size> sizes) {
|
||||
String sizesString = "";
|
||||
if (sizes != null && sizes.size() > 0) {
|
||||
sizesString = sizes.get(0).toString();
|
||||
for (int i = 1; i < sizes.size(); i++) {
|
||||
sizesString += "," + sizes.get(i).toString();
|
||||
}
|
||||
}
|
||||
return sizesString;
|
||||
}
|
||||
|
||||
public final float aspectRatio() {
|
||||
return (float) width / (float) height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(final Size other) {
|
||||
return width * height - other.width * other.height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(other instanceof Size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Size otherSize = (Size) other;
|
||||
return (width == otherSize.width && height == otherSize.height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return width * 32713 + height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return dimensionsAsString(width, height);
|
||||
}
|
||||
|
||||
public static final String dimensionsAsString(final int width, final int height) {
|
||||
return width + "x" + height;
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
==============================================================================*/
|
||||
|
||||
package org.tensorflow.demo.env;
|
||||
|
||||
import android.os.SystemClock;
|
||||
|
||||
/**
|
||||
* A simple utility timer for measuring CPU time and wall-clock splits.
|
||||
*/
|
||||
public class SplitTimer {
|
||||
private final Logger logger;
|
||||
|
||||
private long lastWallTime;
|
||||
private long lastCpuTime;
|
||||
|
||||
public SplitTimer(final String name) {
|
||||
logger = new Logger(name);
|
||||
newSplit();
|
||||
}
|
||||
|
||||
public void newSplit() {
|
||||
lastWallTime = SystemClock.uptimeMillis();
|
||||
lastCpuTime = SystemClock.currentThreadTimeMillis();
|
||||
}
|
||||
|
||||
public void endSplit(final String splitName) {
|
||||
final long currWallTime = SystemClock.uptimeMillis();
|
||||
final long currCpuTime = SystemClock.currentThreadTimeMillis();
|
||||
|
||||
logger.i(
|
||||
"%s: cpu=%dms wall=%dms",
|
||||
splitName, currCpuTime - lastCpuTime, currWallTime - lastWallTime);
|
||||
|
||||
lastWallTime = currWallTime;
|
||||
lastCpuTime = currCpuTime;
|
||||
}
|
||||
}
|
@ -1,421 +0,0 @@
|
||||
/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
==============================================================================*/
|
||||
|
||||
package org.tensorflow.demo.tracking;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Paint.Cap;
|
||||
import android.graphics.Paint.Join;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.graphics.RectF;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
import android.util.TypedValue;
|
||||
import android.widget.Toast;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import org.tensorflow.demo.Classifier.Recognition;
|
||||
import org.tensorflow.demo.env.BorderedText;
|
||||
import org.tensorflow.demo.env.ImageUtils;
|
||||
import org.tensorflow.demo.env.Logger;
|
||||
|
||||
/**
|
||||
* A tracker wrapping ObjectTracker that also handles non-max suppression and matching existing
|
||||
* objects to new detections.
|
||||
*/
|
||||
public class MultiBoxTracker {
|
||||
private final Logger logger = new Logger();
|
||||
|
||||
private static final float TEXT_SIZE_DIP = 18;
|
||||
|
||||
// Maximum percentage of a box that can be overlapped by another box at detection time. Otherwise
|
||||
// the lower scored box (new or old) will be removed.
|
||||
private static final float MAX_OVERLAP = 0.2f;
|
||||
|
||||
private static final float MIN_SIZE = 16.0f;
|
||||
|
||||
// Allow replacement of the tracked box with new results if
|
||||
// correlation has dropped below this level.
|
||||
private static final float MARGINAL_CORRELATION = 0.75f;
|
||||
|
||||
// Consider object to be lost if correlation falls below this threshold.
|
||||
private static final float MIN_CORRELATION = 0.3f;
|
||||
|
||||
private static final int[] COLORS = {
|
||||
Color.BLUE, Color.RED, Color.GREEN, Color.YELLOW, Color.CYAN, Color.MAGENTA, Color.WHITE,
|
||||
Color.parseColor("#55FF55"), Color.parseColor("#FFA500"), Color.parseColor("#FF8888"),
|
||||
Color.parseColor("#AAAAFF"), Color.parseColor("#FFFFAA"), Color.parseColor("#55AAAA"),
|
||||
Color.parseColor("#AA33AA"), Color.parseColor("#0D0068")
|
||||
};
|
||||
|
||||
private final Queue<Integer> availableColors = new LinkedList<Integer>();
|
||||
|
||||
public ObjectTracker objectTracker;
|
||||
|
||||
final List<Pair<Float, RectF>> screenRects = new LinkedList<Pair<Float, RectF>>();
|
||||
|
||||
private static class TrackedRecognition {
|
||||
ObjectTracker.TrackedObject trackedObject;
|
||||
RectF location;
|
||||
float detectionConfidence;
|
||||
int color;
|
||||
String title;
|
||||
}
|
||||
|
||||
private final List<TrackedRecognition> trackedObjects = new LinkedList<TrackedRecognition>();
|
||||
|
||||
private final Paint boxPaint = new Paint();
|
||||
|
||||
private final float textSizePx;
|
||||
private final BorderedText borderedText;
|
||||
|
||||
private Matrix frameToCanvasMatrix;
|
||||
|
||||
private int frameWidth;
|
||||
private int frameHeight;
|
||||
|
||||
private int sensorOrientation;
|
||||
private Context context;
|
||||
|
||||
public MultiBoxTracker(final Context context) {
|
||||
this.context = context;
|
||||
for (final int color : COLORS) {
|
||||
availableColors.add(color);
|
||||
}
|
||||
|
||||
boxPaint.setColor(Color.RED);
|
||||
boxPaint.setStyle(Style.STROKE);
|
||||
boxPaint.setStrokeWidth(12.0f);
|
||||
boxPaint.setStrokeCap(Cap.ROUND);
|
||||
boxPaint.setStrokeJoin(Join.ROUND);
|
||||
boxPaint.setStrokeMiter(100);
|
||||
|
||||
textSizePx =
|
||||
TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, TEXT_SIZE_DIP, context.getResources().getDisplayMetrics());
|
||||
borderedText = new BorderedText(textSizePx);
|
||||
}
|
||||
|
||||
private Matrix getFrameToCanvasMatrix() {
|
||||
return frameToCanvasMatrix;
|
||||
}
|
||||
|
||||
public synchronized void drawDebug(final Canvas canvas) {
|
||||
final Paint textPaint = new Paint();
|
||||
textPaint.setColor(Color.WHITE);
|
||||
textPaint.setTextSize(60.0f);
|
||||
|
||||
final Paint boxPaint = new Paint();
|
||||
boxPaint.setColor(Color.RED);
|
||||
boxPaint.setAlpha(200);
|
||||
boxPaint.setStyle(Style.STROKE);
|
||||
|
||||
for (final Pair<Float, RectF> detection : screenRects) {
|
||||
final RectF rect = detection.second;
|
||||
canvas.drawRect(rect, boxPaint);
|
||||
canvas.drawText("" + detection.first, rect.left, rect.top, textPaint);
|
||||
borderedText.drawText(canvas, rect.centerX(), rect.centerY(), "" + detection.first);
|
||||
}
|
||||
|
||||
if (objectTracker == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw correlations.
|
||||
for (final TrackedRecognition recognition : trackedObjects) {
|
||||
final ObjectTracker.TrackedObject trackedObject = recognition.trackedObject;
|
||||
|
||||
final RectF trackedPos = trackedObject.getTrackedPositionInPreviewFrame();
|
||||
|
||||
if (getFrameToCanvasMatrix().mapRect(trackedPos)) {
|
||||
final String labelString = String.format("%.2f", trackedObject.getCurrentCorrelation());
|
||||
borderedText.drawText(canvas, trackedPos.right, trackedPos.bottom, labelString);
|
||||
}
|
||||
}
|
||||
|
||||
final Matrix matrix = getFrameToCanvasMatrix();
|
||||
objectTracker.drawDebug(canvas, matrix);
|
||||
}
|
||||
|
||||
public synchronized void trackResults(
|
||||
final List<Recognition> results, final byte[] frame, final long timestamp) {
|
||||
logger.i("Processing %d results from %d", results.size(), timestamp);
|
||||
processResults(timestamp, results, frame);
|
||||
}
|
||||
|
||||
public synchronized void draw(final Canvas canvas) {
|
||||
final boolean rotated = sensorOrientation % 180 == 90;
|
||||
final float multiplier =
|
||||
Math.min(canvas.getHeight() / (float) (rotated ? frameWidth : frameHeight),
|
||||
canvas.getWidth() / (float) (rotated ? frameHeight : frameWidth));
|
||||
frameToCanvasMatrix =
|
||||
ImageUtils.getTransformationMatrix(
|
||||
frameWidth,
|
||||
frameHeight,
|
||||
(int) (multiplier * (rotated ? frameHeight : frameWidth)),
|
||||
(int) (multiplier * (rotated ? frameWidth : frameHeight)),
|
||||
sensorOrientation,
|
||||
false);
|
||||
for (final TrackedRecognition recognition : trackedObjects) {
|
||||
final RectF trackedPos =
|
||||
(objectTracker != null)
|
||||
? recognition.trackedObject.getTrackedPositionInPreviewFrame()
|
||||
: new RectF(recognition.location);
|
||||
|
||||
getFrameToCanvasMatrix().mapRect(trackedPos);
|
||||
boxPaint.setColor(recognition.color);
|
||||
|
||||
final float cornerSize = Math.min(trackedPos.width(), trackedPos.height()) / 8.0f;
|
||||
canvas.drawRoundRect(trackedPos, cornerSize, cornerSize, boxPaint);
|
||||
|
||||
final String labelString =
|
||||
!TextUtils.isEmpty(recognition.title)
|
||||
? String.format("%s %.2f", recognition.title, recognition.detectionConfidence)
|
||||
: String.format("%.2f", recognition.detectionConfidence);
|
||||
borderedText.drawText(canvas, trackedPos.left + cornerSize, trackedPos.bottom, labelString);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean initialized = false;
|
||||
|
||||
public synchronized void onFrame(
|
||||
final int w,
|
||||
final int h,
|
||||
final int rowStride,
|
||||
final int sensorOrientation,
|
||||
final byte[] frame,
|
||||
final long timestamp) {
|
||||
if (objectTracker == null && !initialized) {
|
||||
ObjectTracker.clearInstance();
|
||||
|
||||
logger.i("Initializing ObjectTracker: %dx%d", w, h);
|
||||
objectTracker = ObjectTracker.getInstance(w, h, rowStride, true);
|
||||
frameWidth = w;
|
||||
frameHeight = h;
|
||||
this.sensorOrientation = sensorOrientation;
|
||||
initialized = true;
|
||||
|
||||
if (objectTracker == null) {
|
||||
String message =
|
||||
"Object tracking support not found. "
|
||||
+ "See tensorflow/examples/android/README.md for details.";
|
||||
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
|
||||
logger.e(message);
|
||||
}
|
||||
}
|
||||
|
||||
if (objectTracker == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
objectTracker.nextFrame(frame, null, timestamp, null, true);
|
||||
|
||||
// Clean up any objects not worth tracking any more.
|
||||
final LinkedList<TrackedRecognition> copyList =
|
||||
new LinkedList<TrackedRecognition>(trackedObjects);
|
||||
for (final TrackedRecognition recognition : copyList) {
|
||||
final ObjectTracker.TrackedObject trackedObject = recognition.trackedObject;
|
||||
final float correlation = trackedObject.getCurrentCorrelation();
|
||||
if (correlation < MIN_CORRELATION) {
|
||||
logger.v("Removing tracked object %s because NCC is %.2f", trackedObject, correlation);
|
||||
trackedObject.stopTracking();
|
||||
trackedObjects.remove(recognition);
|
||||
|
||||
availableColors.add(recognition.color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processResults(
|
||||
final long timestamp, final List<Recognition> results, final byte[] originalFrame) {
|
||||
final List<Pair<Float, Recognition>> rectsToTrack = new LinkedList<Pair<Float, Recognition>>();
|
||||
|
||||
screenRects.clear();
|
||||
final Matrix rgbFrameToScreen = new Matrix(getFrameToCanvasMatrix());
|
||||
|
||||
for (final Recognition result : results) {
|
||||
if (result.getLocation() == null) {
|
||||
continue;
|
||||
}
|
||||
final RectF detectionFrameRect = new RectF(result.getLocation());
|
||||
|
||||
final RectF detectionScreenRect = new RectF();
|
||||
rgbFrameToScreen.mapRect(detectionScreenRect, detectionFrameRect);
|
||||
|
||||
logger.v(
|
||||
"Result! Frame: " + result.getLocation() + " mapped to screen:" + detectionScreenRect);
|
||||
|
||||
screenRects.add(new Pair<Float, RectF>(result.getConfidence(), detectionScreenRect));
|
||||
|
||||
if (detectionFrameRect.width() < MIN_SIZE || detectionFrameRect.height() < MIN_SIZE) {
|
||||
logger.w("Degenerate rectangle! " + detectionFrameRect);
|
||||
continue;
|
||||
}
|
||||
|
||||
rectsToTrack.add(new Pair<Float, Recognition>(result.getConfidence(), result));
|
||||
}
|
||||
|
||||
if (rectsToTrack.isEmpty()) {
|
||||
logger.v("Nothing to track, aborting.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (objectTracker == null) {
|
||||
trackedObjects.clear();
|
||||
for (final Pair<Float, Recognition> potential : rectsToTrack) {
|
||||
final TrackedRecognition trackedRecognition = new TrackedRecognition();
|
||||
trackedRecognition.detectionConfidence = potential.first;
|
||||
trackedRecognition.location = new RectF(potential.second.getLocation());
|
||||
trackedRecognition.trackedObject = null;
|
||||
trackedRecognition.title = potential.second.getTitle();
|
||||
trackedRecognition.color = COLORS[trackedObjects.size()];
|
||||
trackedObjects.add(trackedRecognition);
|
||||
|
||||
if (trackedObjects.size() >= COLORS.length) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
logger.i("%d rects to track", rectsToTrack.size());
|
||||
for (final Pair<Float, Recognition> potential : rectsToTrack) {
|
||||
handleDetection(originalFrame, timestamp, potential);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDetection(
|
||||
final byte[] frameCopy, final long timestamp, final Pair<Float, Recognition> potential) {
|
||||
final ObjectTracker.TrackedObject potentialObject =
|
||||
objectTracker.trackObject(potential.second.getLocation(), timestamp, frameCopy);
|
||||
|
||||
final float potentialCorrelation = potentialObject.getCurrentCorrelation();
|
||||
logger.v(
|
||||
"Tracked object went from %s to %s with correlation %.2f",
|
||||
potential.second, potentialObject.getTrackedPositionInPreviewFrame(), potentialCorrelation);
|
||||
|
||||
if (potentialCorrelation < MARGINAL_CORRELATION) {
|
||||
logger.v("Correlation too low to begin tracking %s.", potentialObject);
|
||||
potentialObject.stopTracking();
|
||||
return;
|
||||
}
|
||||
|
||||
final List<TrackedRecognition> removeList = new LinkedList<TrackedRecognition>();
|
||||
|
||||
float maxIntersect = 0.0f;
|
||||
|
||||
// This is the current tracked object whose color we will take. If left null we'll take the
|
||||
// first one from the color queue.
|
||||
TrackedRecognition recogToReplace = null;
|
||||
|
||||
// Look for intersections that will be overridden by this object or an intersection that would
|
||||
// prevent this one from being placed.
|
||||
for (final TrackedRecognition trackedRecognition : trackedObjects) {
|
||||
final RectF a = trackedRecognition.trackedObject.getTrackedPositionInPreviewFrame();
|
||||
final RectF b = potentialObject.getTrackedPositionInPreviewFrame();
|
||||
final RectF intersection = new RectF();
|
||||
final boolean intersects = intersection.setIntersect(a, b);
|
||||
|
||||
final float intersectArea = intersection.width() * intersection.height();
|
||||
final float totalArea = a.width() * a.height() + b.width() * b.height() - intersectArea;
|
||||
final float intersectOverUnion = intersectArea / totalArea;
|
||||
|
||||
// If there is an intersection with this currently tracked box above the maximum overlap
|
||||
// percentage allowed, either the new recognition needs to be dismissed or the old
|
||||
// recognition needs to be removed and possibly replaced with the new one.
|
||||
if (intersects && intersectOverUnion > MAX_OVERLAP) {
|
||||
if (potential.first < trackedRecognition.detectionConfidence
|
||||
&& trackedRecognition.trackedObject.getCurrentCorrelation() > MARGINAL_CORRELATION) {
|
||||
// If track for the existing object is still going strong and the detection score was
|
||||
// good, reject this new object.
|
||||
potentialObject.stopTracking();
|
||||
return;
|
||||
} else {
|
||||
removeList.add(trackedRecognition);
|
||||
|
||||
// Let the previously tracked object with max intersection amount donate its color to
|
||||
// the new object.
|
||||
if (intersectOverUnion > maxIntersect) {
|
||||
maxIntersect = intersectOverUnion;
|
||||
recogToReplace = trackedRecognition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we're already tracking the max object and no intersections were found to bump off,
|
||||
// pick the worst current tracked object to remove, if it's also worse than this candidate
|
||||
// object.
|
||||
if (availableColors.isEmpty() && removeList.isEmpty()) {
|
||||
for (final TrackedRecognition candidate : trackedObjects) {
|
||||
if (candidate.detectionConfidence < potential.first) {
|
||||
if (recogToReplace == null
|
||||
|| candidate.detectionConfidence < recogToReplace.detectionConfidence) {
|
||||
// Save it so that we use this color for the new object.
|
||||
recogToReplace = candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (recogToReplace != null) {
|
||||
logger.v("Found non-intersecting object to remove.");
|
||||
removeList.add(recogToReplace);
|
||||
} else {
|
||||
logger.v("No non-intersecting object found to remove");
|
||||
}
|
||||
}
|
||||
|
||||
// Remove everything that got intersected.
|
||||
for (final TrackedRecognition trackedRecognition : removeList) {
|
||||
logger.v(
|
||||
"Removing tracked object %s with detection confidence %.2f, correlation %.2f",
|
||||
trackedRecognition.trackedObject,
|
||||
trackedRecognition.detectionConfidence,
|
||||
trackedRecognition.trackedObject.getCurrentCorrelation());
|
||||
trackedRecognition.trackedObject.stopTracking();
|
||||
trackedObjects.remove(trackedRecognition);
|
||||
if (trackedRecognition != recogToReplace) {
|
||||
availableColors.add(trackedRecognition.color);
|
||||
}
|
||||
}
|
||||
|
||||
if (recogToReplace == null && availableColors.isEmpty()) {
|
||||
logger.e("No room to track this object, aborting.");
|
||||
potentialObject.stopTracking();
|
||||
return;
|
||||
}
|
||||
|
||||
// Finally safe to say we can track this object.
|
||||
logger.v(
|
||||
"Tracking object %s (%s) with detection confidence %.2f at position %s",
|
||||
potentialObject,
|
||||
potential.second.getTitle(),
|
||||
potential.first,
|
||||
potential.second.getLocation());
|
||||
final TrackedRecognition trackedRecognition = new TrackedRecognition();
|
||||
trackedRecognition.detectionConfidence = potential.first;
|
||||
trackedRecognition.trackedObject = potentialObject;
|
||||
trackedRecognition.title = potential.second.getTitle();
|
||||
|
||||
// Use the color from a replaced object before taking one from the color queue.
|
||||
trackedRecognition.color =
|
||||
recogToReplace != null ? recogToReplace.color : availableColors.poll();
|
||||
trackedObjects.add(trackedRecognition);
|
||||
}
|
||||
}
|
@ -1,661 +0,0 @@
|
||||
/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
==============================================================================*/
|
||||
|
||||
package org.tensorflow.demo.tracking;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Typeface;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Vector;
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
import org.tensorflow.demo.env.Logger;
|
||||
import org.tensorflow.demo.env.Size;
|
||||
|
||||
/**
|
||||
* True object detector/tracker class that tracks objects across consecutive preview frames.
|
||||
* It provides a simplified Java interface to the analogous native object defined by
|
||||
* jni/client_vision/tracking/object_tracker.*.
|
||||
*
|
||||
* Currently, the ObjectTracker is a singleton due to native code restrictions, and so must
|
||||
* be allocated by ObjectTracker.getInstance(). In addition, release() should be called
|
||||
* as soon as the ObjectTracker is no longer needed, and before a new one is created.
|
||||
*
|
||||
* nextFrame() should be called as new frames become available, preferably as often as possible.
|
||||
*
|
||||
* After allocation, new TrackedObjects may be instantiated via trackObject(). TrackedObjects
|
||||
* are associated with the ObjectTracker that created them, and are only valid while that
|
||||
* ObjectTracker still exists.
|
||||
*/
|
||||
public class ObjectTracker {
|
||||
private static final Logger LOGGER = new Logger();
|
||||
|
||||
private static boolean libraryFound = false;
|
||||
|
||||
static {
|
||||
try {
|
||||
System.loadLibrary("tensorflow_demo");
|
||||
libraryFound = true;
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
LOGGER.e("libtensorflow_demo.so not found, tracking unavailable");
|
||||
}
|
||||
}
|
||||
|
||||
private static final boolean DRAW_TEXT = false;
|
||||
|
||||
/**
|
||||
* How many history points to keep track of and draw in the red history line.
|
||||
*/
|
||||
private static final int MAX_DEBUG_HISTORY_SIZE = 30;
|
||||
|
||||
/**
|
||||
* How many frames of optical flow deltas to record.
|
||||
* TODO(andrewharp): Push this down to the native level so it can be polled
|
||||
* efficiently into a an array for upload, instead of keeping a duplicate
|
||||
* copy in Java.
|
||||
*/
|
||||
private static final int MAX_FRAME_HISTORY_SIZE = 200;
|
||||
|
||||
private static final int DOWNSAMPLE_FACTOR = 2;
|
||||
|
||||
private final byte[] downsampledFrame;
|
||||
|
||||
protected static ObjectTracker instance;
|
||||
|
||||
private final Map<String, TrackedObject> trackedObjects;
|
||||
|
||||
private long lastTimestamp;
|
||||
|
||||
private FrameChange lastKeypoints;
|
||||
|
||||
private final Vector<PointF> debugHistory;
|
||||
|
||||
private final LinkedList<TimestampedDeltas> timestampedDeltas;
|
||||
|
||||
protected final int frameWidth;
|
||||
protected final int frameHeight;
|
||||
private final int rowStride;
|
||||
protected final boolean alwaysTrack;
|
||||
|
||||
private static class TimestampedDeltas {
|
||||
final long timestamp;
|
||||
final byte[] deltas;
|
||||
|
||||
public TimestampedDeltas(final long timestamp, final byte[] deltas) {
|
||||
this.timestamp = timestamp;
|
||||
this.deltas = deltas;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple class that records keypoint information, which includes
|
||||
* local location, score and type. This will be used in calculating
|
||||
* FrameChange.
|
||||
*/
|
||||
public static class Keypoint {
|
||||
public final float x;
|
||||
public final float y;
|
||||
public final float score;
|
||||
public final int type;
|
||||
|
||||
public Keypoint(final float x, final float y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.score = 0;
|
||||
this.type = -1;
|
||||
}
|
||||
|
||||
public Keypoint(final float x, final float y, final float score, final int type) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.score = score;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
Keypoint delta(final Keypoint other) {
|
||||
return new Keypoint(this.x - other.x, this.y - other.y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple class that could calculate Keypoint delta.
|
||||
* This class will be used in calculating frame translation delta
|
||||
* for optical flow.
|
||||
*/
|
||||
public static class PointChange {
|
||||
public final Keypoint keypointA;
|
||||
public final Keypoint keypointB;
|
||||
Keypoint pointDelta;
|
||||
private final boolean wasFound;
|
||||
|
||||
public PointChange(final float x1, final float y1,
|
||||
final float x2, final float y2,
|
||||
final float score, final int type,
|
||||
final boolean wasFound) {
|
||||
this.wasFound = wasFound;
|
||||
|
||||
keypointA = new Keypoint(x1, y1, score, type);
|
||||
keypointB = new Keypoint(x2, y2);
|
||||
}
|
||||
|
||||
public Keypoint getDelta() {
|
||||
if (pointDelta == null) {
|
||||
pointDelta = keypointB.delta(keypointA);
|
||||
}
|
||||
return pointDelta;
|
||||
}
|
||||
}
|
||||
|
||||
/** A class that records a timestamped frame translation delta for optical flow. */
|
||||
public static class FrameChange {
|
||||
public static final int KEYPOINT_STEP = 7;
|
||||
|
||||
public final Vector<PointChange> pointDeltas;
|
||||
|
||||
private final float minScore;
|
||||
private final float maxScore;
|
||||
|
||||
public FrameChange(final float[] framePoints) {
|
||||
float minScore = 100.0f;
|
||||
float maxScore = -100.0f;
|
||||
|
||||
pointDeltas = new Vector<PointChange>(framePoints.length / KEYPOINT_STEP);
|
||||
|
||||
for (int i = 0; i < framePoints.length; i += KEYPOINT_STEP) {
|
||||
final float x1 = framePoints[i + 0] * DOWNSAMPLE_FACTOR;
|
||||
final float y1 = framePoints[i + 1] * DOWNSAMPLE_FACTOR;
|
||||
|
||||
final boolean wasFound = framePoints[i + 2] > 0.0f;
|
||||
|
||||
final float x2 = framePoints[i + 3] * DOWNSAMPLE_FACTOR;
|
||||
final float y2 = framePoints[i + 4] * DOWNSAMPLE_FACTOR;
|
||||
final float score = framePoints[i + 5];
|
||||
final int type = (int) framePoints[i + 6];
|
||||
|
||||
minScore = Math.min(minScore, score);
|
||||
maxScore = Math.max(maxScore, score);
|
||||
|
||||
pointDeltas.add(new PointChange(x1, y1, x2, y2, score, type, wasFound));
|
||||
}
|
||||
|
||||
this.minScore = minScore;
|
||||
this.maxScore = maxScore;
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized ObjectTracker getInstance(
|
||||
final int frameWidth, final int frameHeight, final int rowStride, final boolean alwaysTrack) {
|
||||
if (!libraryFound) {
|
||||
LOGGER.e(
|
||||
"Native object tracking support not found. "
|
||||
+ "See tensorflow/examples/android/README.md for details.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (instance == null) {
|
||||
instance = new ObjectTracker(frameWidth, frameHeight, rowStride, alwaysTrack);
|
||||
instance.init();
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"Tried to create a new objectracker before releasing the old one!");
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static synchronized void clearInstance() {
|
||||
if (instance != null) {
|
||||
instance.release();
|
||||
}
|
||||
}
|
||||
|
||||
protected ObjectTracker(
|
||||
final int frameWidth, final int frameHeight, final int rowStride, final boolean alwaysTrack) {
|
||||
this.frameWidth = frameWidth;
|
||||
this.frameHeight = frameHeight;
|
||||
this.rowStride = rowStride;
|
||||
this.alwaysTrack = alwaysTrack;
|
||||
this.timestampedDeltas = new LinkedList<TimestampedDeltas>();
|
||||
|
||||
trackedObjects = new HashMap<String, TrackedObject>();
|
||||
|
||||
debugHistory = new Vector<PointF>(MAX_DEBUG_HISTORY_SIZE);
|
||||
|
||||
downsampledFrame =
|
||||
new byte
|
||||
[(frameWidth + DOWNSAMPLE_FACTOR - 1)
|
||||
/ DOWNSAMPLE_FACTOR
|
||||
* (frameWidth + DOWNSAMPLE_FACTOR - 1)
|
||||
/ DOWNSAMPLE_FACTOR];
|
||||
}
|
||||
|
||||
protected void init() {
|
||||
// The native tracker never sees the full frame, so pre-scale dimensions
|
||||
// by the downsample factor.
|
||||
initNative(frameWidth / DOWNSAMPLE_FACTOR, frameHeight / DOWNSAMPLE_FACTOR, alwaysTrack);
|
||||
}
|
||||
|
||||
private final float[] matrixValues = new float[9];
|
||||
|
||||
private long downsampledTimestamp;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public synchronized void drawOverlay(final GL10 gl,
|
||||
final Size cameraViewSize, final Matrix matrix) {
|
||||
final Matrix tempMatrix = new Matrix(matrix);
|
||||
tempMatrix.preScale(DOWNSAMPLE_FACTOR, DOWNSAMPLE_FACTOR);
|
||||
tempMatrix.getValues(matrixValues);
|
||||
drawNative(cameraViewSize.width, cameraViewSize.height, matrixValues);
|
||||
}
|
||||
|
||||
public synchronized void nextFrame(
|
||||
final byte[] frameData, final byte[] uvData,
|
||||
final long timestamp, final float[] transformationMatrix,
|
||||
final boolean updateDebugInfo) {
|
||||
if (downsampledTimestamp != timestamp) {
|
||||
ObjectTracker.downsampleImageNative(
|
||||
frameWidth, frameHeight, rowStride, frameData, DOWNSAMPLE_FACTOR, downsampledFrame);
|
||||
downsampledTimestamp = timestamp;
|
||||
}
|
||||
|
||||
// Do Lucas Kanade using the fullframe initializer.
|
||||
nextFrameNative(downsampledFrame, uvData, timestamp, transformationMatrix);
|
||||
|
||||
timestampedDeltas.add(new TimestampedDeltas(timestamp, getKeypointsPacked(DOWNSAMPLE_FACTOR)));
|
||||
while (timestampedDeltas.size() > MAX_FRAME_HISTORY_SIZE) {
|
||||
timestampedDeltas.removeFirst();
|
||||
}
|
||||
|
||||
for (final TrackedObject trackedObject : trackedObjects.values()) {
|
||||
trackedObject.updateTrackedPosition();
|
||||
}
|
||||
|
||||
if (updateDebugInfo) {
|
||||
updateDebugHistory();
|
||||
}
|
||||
|
||||
lastTimestamp = timestamp;
|
||||
}
|
||||
|
||||
public synchronized void release() {
|
||||
releaseMemoryNative();
|
||||
synchronized (ObjectTracker.class) {
|
||||
instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void drawHistoryDebug(final Canvas canvas) {
|
||||
drawHistoryPoint(
|
||||
canvas, frameWidth * DOWNSAMPLE_FACTOR / 2, frameHeight * DOWNSAMPLE_FACTOR / 2);
|
||||
}
|
||||
|
||||
private void drawHistoryPoint(final Canvas canvas, final float startX, final float startY) {
|
||||
final Paint p = new Paint();
|
||||
p.setAntiAlias(false);
|
||||
p.setTypeface(Typeface.SERIF);
|
||||
|
||||
p.setColor(Color.RED);
|
||||
p.setStrokeWidth(2.0f);
|
||||
|
||||
// Draw the center circle.
|
||||
p.setColor(Color.GREEN);
|
||||
canvas.drawCircle(startX, startY, 3.0f, p);
|
||||
|
||||
p.setColor(Color.RED);
|
||||
|
||||
// Iterate through in backwards order.
|
||||
synchronized (debugHistory) {
|
||||
final int numPoints = debugHistory.size();
|
||||
float lastX = startX;
|
||||
float lastY = startY;
|
||||
for (int keypointNum = 0; keypointNum < numPoints; ++keypointNum) {
|
||||
final PointF delta = debugHistory.get(numPoints - keypointNum - 1);
|
||||
final float newX = lastX + delta.x;
|
||||
final float newY = lastY + delta.y;
|
||||
canvas.drawLine(lastX, lastY, newX, newY, p);
|
||||
lastX = newX;
|
||||
lastY = newY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int floatToChar(final float value) {
|
||||
return Math.max(0, Math.min((int) (value * 255.999f), 255));
|
||||
}
|
||||
|
||||
private void drawKeypointsDebug(final Canvas canvas) {
|
||||
final Paint p = new Paint();
|
||||
if (lastKeypoints == null) {
|
||||
return;
|
||||
}
|
||||
final int keypointSize = 3;
|
||||
|
||||
final float minScore = lastKeypoints.minScore;
|
||||
final float maxScore = lastKeypoints.maxScore;
|
||||
|
||||
for (final PointChange keypoint : lastKeypoints.pointDeltas) {
|
||||
if (keypoint.wasFound) {
|
||||
final int r =
|
||||
floatToChar((keypoint.keypointA.score - minScore) / (maxScore - minScore));
|
||||
final int b =
|
||||
floatToChar(1.0f - (keypoint.keypointA.score - minScore) / (maxScore - minScore));
|
||||
|
||||
final int color = 0xFF000000 | (r << 16) | b;
|
||||
p.setColor(color);
|
||||
|
||||
final float[] screenPoints = {keypoint.keypointA.x, keypoint.keypointA.y,
|
||||
keypoint.keypointB.x, keypoint.keypointB.y};
|
||||
canvas.drawRect(screenPoints[2] - keypointSize,
|
||||
screenPoints[3] - keypointSize,
|
||||
screenPoints[2] + keypointSize,
|
||||
screenPoints[3] + keypointSize, p);
|
||||
p.setColor(Color.CYAN);
|
||||
canvas.drawLine(screenPoints[2], screenPoints[3],
|
||||
screenPoints[0], screenPoints[1], p);
|
||||
|
||||
if (DRAW_TEXT) {
|
||||
p.setColor(Color.WHITE);
|
||||
canvas.drawText(keypoint.keypointA.type + ": " + keypoint.keypointA.score,
|
||||
keypoint.keypointA.x, keypoint.keypointA.y, p);
|
||||
}
|
||||
} else {
|
||||
p.setColor(Color.YELLOW);
|
||||
final float[] screenPoint = {keypoint.keypointA.x, keypoint.keypointA.y};
|
||||
canvas.drawCircle(screenPoint[0], screenPoint[1], 5.0f, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized PointF getAccumulatedDelta(final long timestamp, final float positionX,
|
||||
final float positionY, final float radius) {
|
||||
final RectF currPosition = getCurrentPosition(timestamp,
|
||||
new RectF(positionX - radius, positionY - radius, positionX + radius, positionY + radius));
|
||||
return new PointF(currPosition.centerX() - positionX, currPosition.centerY() - positionY);
|
||||
}
|
||||
|
||||
private synchronized RectF getCurrentPosition(final long timestamp, final RectF
|
||||
oldPosition) {
|
||||
final RectF downscaledFrameRect = downscaleRect(oldPosition);
|
||||
|
||||
final float[] delta = new float[4];
|
||||
getCurrentPositionNative(timestamp, downscaledFrameRect.left, downscaledFrameRect.top,
|
||||
downscaledFrameRect.right, downscaledFrameRect.bottom, delta);
|
||||
|
||||
final RectF newPosition = new RectF(delta[0], delta[1], delta[2], delta[3]);
|
||||
|
||||
return upscaleRect(newPosition);
|
||||
}
|
||||
|
||||
private void updateDebugHistory() {
|
||||
lastKeypoints = new FrameChange(getKeypointsNative(false));
|
||||
|
||||
if (lastTimestamp == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final PointF delta =
|
||||
getAccumulatedDelta(
|
||||
lastTimestamp, frameWidth / DOWNSAMPLE_FACTOR, frameHeight / DOWNSAMPLE_FACTOR, 100);
|
||||
|
||||
synchronized (debugHistory) {
|
||||
debugHistory.add(delta);
|
||||
|
||||
while (debugHistory.size() > MAX_DEBUG_HISTORY_SIZE) {
|
||||
debugHistory.remove(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void drawDebug(final Canvas canvas, final Matrix frameToCanvas) {
|
||||
canvas.save();
|
||||
canvas.setMatrix(frameToCanvas);
|
||||
|
||||
drawHistoryDebug(canvas);
|
||||
drawKeypointsDebug(canvas);
|
||||
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
public Vector<String> getDebugText() {
|
||||
final Vector<String> lines = new Vector<String>();
|
||||
|
||||
if (lastKeypoints != null) {
|
||||
lines.add("Num keypoints " + lastKeypoints.pointDeltas.size());
|
||||
lines.add("Min score: " + lastKeypoints.minScore);
|
||||
lines.add("Max score: " + lastKeypoints.maxScore);
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
public synchronized List<byte[]> pollAccumulatedFlowData(final long endFrameTime) {
|
||||
final List<byte[]> frameDeltas = new ArrayList<byte[]>();
|
||||
while (timestampedDeltas.size() > 0) {
|
||||
final TimestampedDeltas currentDeltas = timestampedDeltas.peek();
|
||||
if (currentDeltas.timestamp <= endFrameTime) {
|
||||
frameDeltas.add(currentDeltas.deltas);
|
||||
timestampedDeltas.removeFirst();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return frameDeltas;
|
||||
}
|
||||
|
||||
private RectF downscaleRect(final RectF fullFrameRect) {
|
||||
return new RectF(
|
||||
fullFrameRect.left / DOWNSAMPLE_FACTOR,
|
||||
fullFrameRect.top / DOWNSAMPLE_FACTOR,
|
||||
fullFrameRect.right / DOWNSAMPLE_FACTOR,
|
||||
fullFrameRect.bottom / DOWNSAMPLE_FACTOR);
|
||||
}
|
||||
|
||||
private RectF upscaleRect(final RectF downsampledFrameRect) {
|
||||
return new RectF(
|
||||
downsampledFrameRect.left * DOWNSAMPLE_FACTOR,
|
||||
downsampledFrameRect.top * DOWNSAMPLE_FACTOR,
|
||||
downsampledFrameRect.right * DOWNSAMPLE_FACTOR,
|
||||
downsampledFrameRect.bottom * DOWNSAMPLE_FACTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* A TrackedObject represents a native TrackedObject, and provides access to the
|
||||
* relevant native tracking information available after every frame update. They may
|
||||
* be safely passed around and accessed externally, but will become invalid after
|
||||
* stopTracking() is called or the related creating ObjectTracker is deactivated.
|
||||
*
|
||||
* @author andrewharp@google.com (Andrew Harp)
|
||||
*/
|
||||
public class TrackedObject {
|
||||
private final String id;
|
||||
|
||||
private long lastExternalPositionTime;
|
||||
|
||||
private RectF lastTrackedPosition;
|
||||
private boolean visibleInLastFrame;
|
||||
|
||||
private boolean isDead;
|
||||
|
||||
TrackedObject(final RectF position, final long timestamp, final byte[] data) {
|
||||
isDead = false;
|
||||
|
||||
id = Integer.toString(this.hashCode());
|
||||
|
||||
lastExternalPositionTime = timestamp;
|
||||
|
||||
synchronized (ObjectTracker.this) {
|
||||
registerInitialAppearance(position, data);
|
||||
setPreviousPosition(position, timestamp);
|
||||
trackedObjects.put(id, this);
|
||||
}
|
||||
}
|
||||
|
||||
public void stopTracking() {
|
||||
checkValidObject();
|
||||
|
||||
synchronized (ObjectTracker.this) {
|
||||
isDead = true;
|
||||
forgetNative(id);
|
||||
trackedObjects.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
public float getCurrentCorrelation() {
|
||||
checkValidObject();
|
||||
return ObjectTracker.this.getCurrentCorrelation(id);
|
||||
}
|
||||
|
||||
void registerInitialAppearance(final RectF position, final byte[] data) {
|
||||
final RectF externalPosition = downscaleRect(position);
|
||||
registerNewObjectWithAppearanceNative(id,
|
||||
externalPosition.left, externalPosition.top,
|
||||
externalPosition.right, externalPosition.bottom,
|
||||
data);
|
||||
}
|
||||
|
||||
synchronized void setPreviousPosition(final RectF position, final long timestamp) {
|
||||
checkValidObject();
|
||||
synchronized (ObjectTracker.this) {
|
||||
if (lastExternalPositionTime > timestamp) {
|
||||
LOGGER.w("Tried to use older position time!");
|
||||
return;
|
||||
}
|
||||
final RectF externalPosition = downscaleRect(position);
|
||||
lastExternalPositionTime = timestamp;
|
||||
|
||||
setPreviousPositionNative(id,
|
||||
externalPosition.left, externalPosition.top,
|
||||
externalPosition.right, externalPosition.bottom,
|
||||
lastExternalPositionTime);
|
||||
|
||||
updateTrackedPosition();
|
||||
}
|
||||
}
|
||||
|
||||
void setCurrentPosition(final RectF position) {
|
||||
checkValidObject();
|
||||
final RectF downsampledPosition = downscaleRect(position);
|
||||
synchronized (ObjectTracker.this) {
|
||||
setCurrentPositionNative(id,
|
||||
downsampledPosition.left, downsampledPosition.top,
|
||||
downsampledPosition.right, downsampledPosition.bottom);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void updateTrackedPosition() {
|
||||
checkValidObject();
|
||||
|
||||
final float[] delta = new float[4];
|
||||
getTrackedPositionNative(id, delta);
|
||||
lastTrackedPosition = new RectF(delta[0], delta[1], delta[2], delta[3]);
|
||||
|
||||
visibleInLastFrame = isObjectVisible(id);
|
||||
}
|
||||
|
||||
public synchronized RectF getTrackedPositionInPreviewFrame() {
|
||||
checkValidObject();
|
||||
|
||||
if (lastTrackedPosition == null) {
|
||||
return null;
|
||||
}
|
||||
return upscaleRect(lastTrackedPosition);
|
||||
}
|
||||
|
||||
synchronized long getLastExternalPositionTime() {
|
||||
return lastExternalPositionTime;
|
||||
}
|
||||
|
||||
public synchronized boolean visibleInLastPreviewFrame() {
|
||||
return visibleInLastFrame;
|
||||
}
|
||||
|
||||
private void checkValidObject() {
|
||||
if (isDead) {
|
||||
throw new RuntimeException("TrackedObject already removed from tracking!");
|
||||
} else if (ObjectTracker.this != instance) {
|
||||
throw new RuntimeException("TrackedObject created with another ObjectTracker!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized TrackedObject trackObject(
|
||||
final RectF position, final long timestamp, final byte[] frameData) {
|
||||
if (downsampledTimestamp != timestamp) {
|
||||
ObjectTracker.downsampleImageNative(
|
||||
frameWidth, frameHeight, rowStride, frameData, DOWNSAMPLE_FACTOR, downsampledFrame);
|
||||
downsampledTimestamp = timestamp;
|
||||
}
|
||||
return new TrackedObject(position, timestamp, downsampledFrame);
|
||||
}
|
||||
|
||||
public synchronized TrackedObject trackObject(final RectF position, final byte[] frameData) {
|
||||
return new TrackedObject(position, lastTimestamp, frameData);
|
||||
}
|
||||
|
||||
/** ********************* NATIVE CODE ************************************ */
|
||||
|
||||
/** This will contain an opaque pointer to the native ObjectTracker */
|
||||
private long nativeObjectTracker;
|
||||
|
||||
private native void initNative(int imageWidth, int imageHeight, boolean alwaysTrack);
|
||||
|
||||
protected native void registerNewObjectWithAppearanceNative(
|
||||
String objectId, float x1, float y1, float x2, float y2, byte[] data);
|
||||
|
||||
protected native void setPreviousPositionNative(
|
||||
String objectId, float x1, float y1, float x2, float y2, long timestamp);
|
||||
|
||||
protected native void setCurrentPositionNative(
|
||||
String objectId, float x1, float y1, float x2, float y2);
|
||||
|
||||
protected native void forgetNative(String key);
|
||||
|
||||
protected native String getModelIdNative(String key);
|
||||
|
||||
protected native boolean haveObject(String key);
|
||||
protected native boolean isObjectVisible(String key);
|
||||
protected native float getCurrentCorrelation(String key);
|
||||
|
||||
protected native float getMatchScore(String key);
|
||||
|
||||
protected native void getTrackedPositionNative(String key, float[] points);
|
||||
|
||||
protected native void nextFrameNative(
|
||||
byte[] frameData, byte[] uvData, long timestamp, float[] frameAlignMatrix);
|
||||
|
||||
protected native void releaseMemoryNative();
|
||||
|
||||
protected native void getCurrentPositionNative(long timestamp,
|
||||
final float positionX1, final float positionY1,
|
||||
final float positionX2, final float positionY2,
|
||||
final float[] delta);
|
||||
|
||||
protected native byte[] getKeypointsPacked(float scaleFactor);
|
||||
|
||||
protected native float[] getKeypointsNative(boolean onlyReturnCorrespondingKeypoints);
|
||||
|
||||
protected native void drawNative(int viewWidth, int viewHeight, float[] frameToCanvas);
|
||||
|
||||
protected static native void downsampleImageNative(
|
||||
int width, int height, int rowStride, byte[] input, int factor, byte[] output);
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright 2017 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
-->
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:ordering="sequentially">
|
||||
<objectAnimator
|
||||
android:propertyName="backgroundColor"
|
||||
android:duration="375"
|
||||
android:valueFrom="0x00b3ccff"
|
||||
android:valueTo="0xffb3ccff"
|
||||
android:valueType="colorType"/>
|
||||
<objectAnimator
|
||||
android:propertyName="backgroundColor"
|
||||
android:duration="375"
|
||||
android:valueFrom="0xffb3ccff"
|
||||
android:valueTo="0x00b3ccff"
|
||||
android:valueType="colorType"/>
|
||||
</set>
|
Binary file not shown.
Before ![]() (image error) Size: 1.0 KiB |
Binary file not shown.
Before ![]() (image error) Size: 4.2 KiB |
Binary file not shown.
Before ![]() (image error) Size: 196 B |
Binary file not shown.
Before ![]() (image error) Size: 665 B |
Binary file not shown.
Before ![]() (image error) Size: 2.2 KiB |
Binary file not shown.
Before ![]() (image error) Size: 1.3 KiB |
Binary file not shown.
Before ![]() (image error) Size: 6.5 KiB |
Binary file not shown.
Before ![]() (image error) Size: 2.2 KiB |
Binary file not shown.
Before ![]() (image error) Size: 12 KiB |
@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright 2017 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
|
||||
<solid android:color="#00000000" />
|
||||
<stroke android:width="1dip" android:color="#cccccc" />
|
||||
</shape>
|
@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#000"
|
||||
tools:context="org.tensorflow.demo.CameraActivity" />
|
@ -1,55 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright 2017 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
-->
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="org.tensorflow.demo.SpeechActivity">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Say one of the words below!"
|
||||
android:id="@+id/textView"
|
||||
android:textAlignment="center"
|
||||
android:layout_gravity="top"
|
||||
android:textSize="24dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
/>
|
||||
|
||||
<ListView
|
||||
android:id="@+id/list_view"
|
||||
android:layout_width="240dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/border"
|
||||
android:layout_gravity="top|center_horizontal"
|
||||
android:textAlignment="center"
|
||||
android:layout_marginTop="100dp"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/quit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Quit"
|
||||
android:layout_gravity="bottom|center_horizontal"
|
||||
android:layout_marginBottom="10dp"
|
||||
/>
|
||||
|
||||
</FrameLayout>
|
@ -1,38 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
-->
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.tensorflow.demo.AutoFitTextureView
|
||||
android:id="@+id/texture"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true" />
|
||||
|
||||
<org.tensorflow.demo.RecognitionScoreView
|
||||
android:id="@+id/results"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="112dp"
|
||||
android:layout_alignParentTop="true" />
|
||||
|
||||
<org.tensorflow.demo.OverlayView
|
||||
android:id="@+id/debug_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentBottom="true" />
|
||||
|
||||
</RelativeLayout>
|
@ -1,51 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
-->
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<org.tensorflow.demo.AutoFitTextureView
|
||||
android:id="@+id/texture"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/black"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#FF000000" />
|
||||
|
||||
<GridView
|
||||
android:id="@+id/grid_layout"
|
||||
android:numColumns="7"
|
||||
android:stretchMode="columnWidth"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<org.tensorflow.demo.OverlayView
|
||||
android:id="@+id/overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentTop="true" />
|
||||
|
||||
<org.tensorflow.demo.OverlayView
|
||||
android:id="@+id/debug_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentTop="true" />
|
||||
</RelativeLayout>
|
@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.tensorflow.demo.AutoFitTextureView
|
||||
android:id="@+id/texture"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<org.tensorflow.demo.OverlayView
|
||||
android:id="@+id/tracking_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<org.tensorflow.demo.OverlayView
|
||||
android:id="@+id/debug_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
</FrameLayout>
|
@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright 2017 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
-->
|
||||
<TextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/list_text_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="TextView"
|
||||
android:textSize="24dp"
|
||||
android:textAlignment="center"
|
||||
android:gravity="center_horizontal"
|
||||
/>
|
@ -1,24 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
||||
<!-- Semantic definitions -->
|
||||
|
||||
<dimen name="horizontal_page_margin">@dimen/margin_huge</dimen>
|
||||
<dimen name="vertical_page_margin">@dimen/margin_medium</dimen>
|
||||
|
||||
</resources>
|
@ -1,25 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
||||
<style name="Widget.SampleMessage">
|
||||
<item name="android:textAppearance">?android:textAppearanceLarge</item>
|
||||
<item name="android:lineSpacingMultiplier">1.2</item>
|
||||
<item name="android:shadowDy">-6.5</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!--
|
||||
Base application theme for API 11+. This theme completely replaces
|
||||
AppBaseTheme from res/values/styles.xml on API 11+ devices.
|
||||
-->
|
||||
<style name="AppBaseTheme" parent="android:Theme.Holo.Light">
|
||||
<!-- API 11 theme customizations can go here. -->
|
||||
</style>
|
||||
|
||||
<style name="FullscreenTheme" parent="android:Theme.Holo">
|
||||
<item name="android:actionBarStyle">@style/FullscreenActionBarStyle</item>
|
||||
<item name="android:windowActionBarOverlay">true</item>
|
||||
<item name="android:windowBackground">@null</item>
|
||||
<item name="metaButtonBarStyle">?android:attr/buttonBarStyle</item>
|
||||
<item name="metaButtonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
|
||||
</style>
|
||||
|
||||
<style name="FullscreenActionBarStyle" parent="android:Widget.Holo.ActionBar">
|
||||
<!-- <item name="android:background">@color/black_overlay</item> -->
|
||||
</style>
|
||||
|
||||
</resources>
|
@ -1,22 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
||||
<!-- Activity themes -->
|
||||
<style name="Theme.Base" parent="android:Theme.Holo.Light" />
|
||||
|
||||
</resources>
|
@ -1,12 +0,0 @@
|
||||
<resources>
|
||||
|
||||
<!--
|
||||
Base application theme for API 14+. This theme completely replaces
|
||||
AppBaseTheme from BOTH res/values/styles.xml and
|
||||
res/values-v11/styles.xml on API 14+ devices.
|
||||
-->
|
||||
<style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
|
||||
<!-- API 14 theme customizations can go here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright 2013 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
||||
|
||||
</resources>
|
@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
||||
<!-- Activity themes -->
|
||||
<style name="Theme.Base" parent="android:Theme.Material.Light">
|
||||
</style>
|
||||
|
||||
</resources>
|
@ -1,14 +0,0 @@
|
||||
<resources>
|
||||
|
||||
<!--
|
||||
Declare custom theme attributes that allow changing which styles are
|
||||
used for button bars depending on the API level.
|
||||
?android:attr/buttonBarStyle is new as of API 11 so this is
|
||||
necessary to support previous API levels.
|
||||
-->
|
||||
<declare-styleable name="ButtonBarContainerTheme">
|
||||
<attr name="metaButtonBarStyle" format="reference" />
|
||||
<attr name="metaButtonBarButtonStyle" format="reference" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<string name="app_name">TFLite Demo</string>
|
||||
<string name="activity_name_classification">TFL Classify</string>
|
||||
<string name="activity_name_detection">TFL Detect</string>
|
||||
<string name="activity_name_speech">TFL Speech</string>
|
||||
</resources>
|
@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2015 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
-->
|
||||
<resources>
|
||||
<color name="control_background">#cc4285f4</color>
|
||||
</resources>
|
@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
-->
|
||||
<resources>
|
||||
<string name="description_info">Info</string>
|
||||
<string name="request_permission">This sample needs camera permission.</string>
|
||||
<string name="camera_error">This device doesn\'t support Camera2 API.</string>
|
||||
</resources>
|
@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
-->
|
||||
<resources>
|
||||
<style name="MaterialTheme" parent="android:Theme.Material.Light.NoActionBar.Fullscreen" />
|
||||
</resources>
|
@ -1,32 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
||||
<!-- Define standard dimensions to comply with Holo-style grids and rhythm. -->
|
||||
|
||||
<dimen name="margin_tiny">4dp</dimen>
|
||||
<dimen name="margin_small">8dp</dimen>
|
||||
<dimen name="margin_medium">16dp</dimen>
|
||||
<dimen name="margin_large">32dp</dimen>
|
||||
<dimen name="margin_huge">64dp</dimen>
|
||||
|
||||
<!-- Semantic definitions -->
|
||||
|
||||
<dimen name="horizontal_page_margin">@dimen/margin_medium</dimen>
|
||||
<dimen name="vertical_page_margin">@dimen/margin_medium</dimen>
|
||||
|
||||
</resources>
|
@ -1,42 +0,0 @@
|
||||
<!--
|
||||
Copyright 2013 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
||||
<!-- Activity themes -->
|
||||
|
||||
<style name="Theme.Base" parent="android:Theme.Light" />
|
||||
|
||||
<style name="Theme.Sample" parent="Theme.Base" />
|
||||
|
||||
<style name="AppTheme" parent="Theme.Sample" />
|
||||
<!-- Widget styling -->
|
||||
|
||||
<style name="Widget" />
|
||||
|
||||
<style name="Widget.SampleMessage">
|
||||
<item name="android:textAppearance">?android:textAppearanceMedium</item>
|
||||
<item name="android:lineSpacingMultiplier">1.1</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.SampleMessageTile">
|
||||
<item name="android:background">@drawable/tile</item>
|
||||
<item name="android:shadowColor">#7F000000</item>
|
||||
<item name="android:shadowDy">-3.5</item>
|
||||
<item name="android:shadowRadius">2</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
@ -1,26 +0,0 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.2.1'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||
// Changed since default name 'build' conflicts with
|
||||
// bazel BUILD file name.
|
||||
buildDir = "gradle-build"
|
@ -1 +0,0 @@
|
||||
include ':app'
|
@ -440,7 +440,6 @@ cmd_status(){
|
||||
do_bazel_nobuild() {
|
||||
BUILD_TARGET="//tensorflow/..."
|
||||
BUILD_TARGET="${BUILD_TARGET} -//tensorflow/lite/delegates/gpu/..."
|
||||
BUILD_TARGET="${BUILD_TARGET} -//tensorflow/lite/examples/android/..."
|
||||
BUILD_TARGET="${BUILD_TARGET} -//tensorflow/lite/java/demo/app/..."
|
||||
BUILD_TARGET="${BUILD_TARGET} -//tensorflow/lite/schema/..."
|
||||
BUILD_CMD="bazel build --nobuild ${BAZEL_FLAGS} -- ${BUILD_TARGET}"
|
||||
|
@ -36,7 +36,6 @@ BUILD_BLACKLIST = [
|
||||
"tensorflow/lite/delegates/gpu",
|
||||
"tensorflow/lite/delegates/gpu/metal",
|
||||
"tensorflow/lite/delegates/gpu/metal/kernels",
|
||||
"tensorflow/lite/examples/android",
|
||||
"tensorflow/lite/experimental/objc",
|
||||
"tensorflow/lite/experimental/swift",
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user