├── .gitignore
├── .idea
├── caches
│ └── build_file_checksums.ser
├── codeStyles
│ └── Project.xml
├── compiler.xml
├── encodings.xml
├── gradle.xml
├── misc.xml
├── runConfigurations.xml
└── vcs.xml
├── .travis.yml
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── assets
│ └── deeplabv3_257_mv_gpu.tflite
│ ├── java
│ └── pp
│ │ └── imagesegmenter
│ │ ├── AutoFitTextureView.java
│ │ ├── CameraActivity.java
│ │ ├── CameraConnectionFragment.java
│ │ ├── Deeplab.java
│ │ ├── MainActivity.java
│ │ ├── OverlayView.java
│ │ ├── env
│ │ ├── BorderedText.java
│ │ ├── ImageUtils.java
│ │ ├── Logger.java
│ │ └── Size.java
│ │ └── tracking
│ │ ├── MultiBoxTracker.java
│ │ └── ObjectTracker.java
│ ├── jni
│ ├── CMakeLists.txt
│ ├── imageutils_jni.cc
│ ├── object_tracking
│ │ ├── config.h
│ │ ├── flow_cache.h
│ │ ├── frame_pair.cc
│ │ ├── frame_pair.h
│ │ ├── geom.h
│ │ ├── gl_utils.h
│ │ ├── image-inl.h
│ │ ├── image.h
│ │ ├── image_data.h
│ │ ├── image_neon.cc
│ │ ├── image_utils.h
│ │ ├── integral_image.h
│ │ ├── jni_utils.h
│ │ ├── keypoint.h
│ │ ├── keypoint_detector.cc
│ │ ├── keypoint_detector.h
│ │ ├── logging.cc
│ │ ├── logging.h
│ │ ├── object_detector.cc
│ │ ├── object_detector.h
│ │ ├── object_model.h
│ │ ├── object_tracker.cc
│ │ ├── object_tracker.h
│ │ ├── object_tracker_jni.cc
│ │ ├── optical_flow.cc
│ │ ├── optical_flow.h
│ │ ├── sprite.h
│ │ ├── time_log.cc
│ │ ├── time_log.h
│ │ ├── tracked_object.cc
│ │ ├── tracked_object.h
│ │ ├── utils.h
│ │ └── utils_neon.cc
│ ├── rgb2yuv.cc
│ ├── rgb2yuv.h
│ ├── yuv2rgb.cc
│ └── yuv2rgb.h
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ └── ic_launcher_background.xml
│ ├── layout
│ ├── activity_camera.xml
│ └── camera_connection_fragment_tracking.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── demo.gif
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/caches/build_file_checksums.ser
6 | /.idea/dictionaries
7 | /.idea/libraries
8 | /.idea/assetWizardSettings.xml
9 | /.idea/gradle.xml
10 | /.idea/modules.xml
11 | /.idea/tasks.xml
12 | /.idea/workspace.xml
13 | .DS_Store
14 | /build
15 | /captures
16 | .externalNativeBuild
17 |
--------------------------------------------------------------------------------
/.idea/caches/build_file_checksums.ser:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pillarpond/image-segmenter-android/36e1dd245dca25c8f44e9e217ca280a3a1bf9f1c/.idea/caches/build_file_checksums.ser
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 |
3 | jdk: oraclejdk8
4 |
5 | android:
6 | components:
7 | - tools
8 | - platform-tools
9 | - tools
10 | - build-tools-28.0.3
11 | - android-28
12 | - extra-android-m2repository
13 | - extra-google-m2repository
14 | install:
15 | - echo y | sdkmanager "ndk-bundle"
16 | - echo y | sdkmanager "cmake;3.10.2.4988404"
17 | - echo y | sdkmanager "lldb;3.1"
18 | # - sdkmanager --update
19 | before_script:
20 | - export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle
21 |
22 | script:
23 | - "./gradlew assembleDebug"
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Realtime Image Segmenter
2 |
3 | [](https://travis-ci.org/pillarpond/image-segmenter-android)
4 |
5 | This sample demonstrates realtime image segmentation on Android. The project is based on the [Deeplab](http://liangchiehchen.com/projects/DeepLab.html)
6 |
7 | ## Model
8 | Tensorflow provide deeplab models pretrained several datasets. In this project, I used [mobilenetv2_coco_voc_trainaug](http://download.tensorflow.org/models/deeplabv3_mnv2_pascal_train_aug_2018_01_29.tar.gz)
9 |
10 | ## Inspiration
11 | The project is heavily inspired by
12 | * [Deeplab](https://github.com/tensorflow/models/tree/master/research/deeplab)
13 | * [DeepLab on Android](https://github.com/dailystudio/ml/tree/master/deeplab)
14 | * [Tensorflow Android Camera Demo](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/android)
15 |
16 | ## Screenshots
17 | 
18 |
19 | ## Pre-trained model
20 | [DeepLab segmentation](https://ai.googleblog.com/2018/03/semantic-image-segmentation-with.html) (257x257) [[download]](https://storage.googleapis.com/download.tensorflow.org/models/tflite/gpu/deeplabv3_257_mv_gpu.tflite)
21 |
22 | ## License
23 | [Apache License 2.0](./LICENSE)
24 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "pp.imagesegmenter"
7 | minSdkVersion 25
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0"
11 | }
12 | buildTypes {
13 | release {
14 | minifyEnabled false
15 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
16 | }
17 | }
18 | externalNativeBuild {
19 | cmake {
20 | path 'src/main/jni/CMakeLists.txt'
21 | }
22 | }
23 | aaptOptions {
24 | noCompress "tflite"
25 | }
26 | compileOptions {
27 | sourceCompatibility JavaVersion.VERSION_1_8
28 | targetCompatibility JavaVersion.VERSION_1_8
29 | }
30 | }
31 |
32 | dependencies {
33 | implementation 'androidx.annotation:annotation:1.1.0'
34 | implementation 'androidx.appcompat:appcompat:1.0.2'
35 | implementation 'com.google.android.material:material:1.1.0-alpha07'
36 | implementation 'org.tensorflow:tensorflow-lite:0.0.0-nightly'
37 | implementation 'org.tensorflow:tensorflow-lite-gpu:0.0.0-nightly'
38 | }
39 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
18 |
19 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/assets/deeplabv3_257_mv_gpu.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pillarpond/image-segmenter-android/36e1dd245dca25c8f44e9e217ca280a3a1bf9f1c/app/src/main/assets/deeplabv3_257_mv_gpu.tflite
--------------------------------------------------------------------------------
/app/src/main/java/pp/imagesegmenter/AutoFitTextureView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 The TensorFlow Authors. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package pp.imagesegmenter;
18 |
19 | import android.content.Context;
20 | import android.util.AttributeSet;
21 | import android.view.TextureView;
22 |
23 | /**
24 | * A {@link TextureView} that can be adjusted to a specified aspect ratio.
25 | */
26 | public class AutoFitTextureView extends TextureView {
27 | private int ratioWidth = 0;
28 | private int ratioHeight = 0;
29 |
30 | public AutoFitTextureView(final Context context) {
31 | this(context, null);
32 | }
33 |
34 | public AutoFitTextureView(final Context context, final AttributeSet attrs) {
35 | this(context, attrs, 0);
36 | }
37 |
38 | public AutoFitTextureView(final Context context, final AttributeSet attrs, final int defStyle) {
39 | super(context, attrs, defStyle);
40 | }
41 |
42 | /**
43 | * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
44 | * calculated from the parameters. Note that the actual sizes of parameters don't matter, that
45 | * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
46 | *
47 | * @param width Relative horizontal size
48 | * @param height Relative vertical size
49 | */
50 | public void setAspectRatio(final int width, final int height) {
51 | if (width < 0 || height < 0) {
52 | throw new IllegalArgumentException("Size cannot be negative.");
53 | }
54 | ratioWidth = width;
55 | ratioHeight = height;
56 | requestLayout();
57 | }
58 |
59 | @Override
60 | protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
61 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
62 | final int width = MeasureSpec.getSize(widthMeasureSpec);
63 | final int height = MeasureSpec.getSize(heightMeasureSpec);
64 | if (0 == ratioWidth || 0 == ratioHeight) {
65 | setMeasuredDimension(width, height);
66 | } else {
67 | if (width < height * ratioWidth / ratioHeight) {
68 | setMeasuredDimension(width, width * ratioHeight / ratioWidth);
69 | } else {
70 | setMeasuredDimension(height * ratioWidth / ratioHeight, height);
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/src/main/java/pp/imagesegmenter/MainActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 The TensorFlow Authors. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package pp.imagesegmenter;
18 |
19 | import android.graphics.Bitmap;
20 | import android.graphics.Bitmap.Config;
21 | import android.graphics.Canvas;
22 | import android.graphics.Color;
23 | import android.graphics.Matrix;
24 | import android.graphics.Paint;
25 | import android.graphics.Typeface;
26 | import android.media.ImageReader.OnImageAvailableListener;
27 | import android.os.SystemClock;
28 | import android.util.Log;
29 | import android.util.Size;
30 | import android.util.TypedValue;
31 | import android.widget.FrameLayout;
32 |
33 | import com.google.android.material.snackbar.Snackbar;
34 |
35 | import pp.imagesegmenter.env.BorderedText;
36 | import pp.imagesegmenter.env.ImageUtils;
37 | import pp.imagesegmenter.env.Logger;
38 | import pp.imagesegmenter.tracking.MultiBoxTracker;
39 |
40 | import java.util.Collections;
41 | import java.util.List;
42 | import java.util.Vector;
43 |
44 | /**
45 | * An activity that uses a Deeplab and ObjectTracker to segment and then track objects.
46 | */
47 | public class MainActivity extends CameraActivity implements OnImageAvailableListener {
48 | private static final Logger LOGGER = new Logger();
49 |
50 | private static final int CROP_SIZE = 257;
51 |
52 | private static final Size DESIRED_PREVIEW_SIZE = new Size(640, 480);
53 |
54 | private static final boolean SAVE_PREVIEW_BITMAP = false;
55 | private static final float TEXT_SIZE_DIP = 10;
56 |
57 | private Integer sensorOrientation;
58 |
59 | private Deeplab deeplab;
60 |
61 | private long lastProcessingTimeMs;
62 | private Bitmap rgbFrameBitmap = null;
63 | private Bitmap croppedBitmap = null;
64 | private Bitmap cropCopyBitmap = null;
65 |
66 | private boolean computingDetection = false;
67 |
68 | private long timestamp = 0;
69 |
70 | private Matrix frameToCropTransform;
71 | private Matrix cropToFrameTransform;
72 |
73 | private MultiBoxTracker tracker;
74 |
75 | private byte[] luminanceCopy;
76 |
77 | private BorderedText borderedText;
78 |
79 | private Snackbar initSnackbar;
80 |
81 | private boolean initialized = false;
82 |
83 | @Override
84 | public void onPreviewSizeChosen(final Size size, final int rotation) {
85 | sensorOrientation = rotation - getScreenOrientation();
86 | LOGGER.i("Camera orientation relative to screen canvas: %d", sensorOrientation);
87 |
88 | FrameLayout container = findViewById(R.id.container);
89 | initSnackbar = Snackbar.make(container, "Initializing...", Snackbar.LENGTH_INDEFINITE);
90 |
91 | init();
92 |
93 | final float textSizePx =
94 | TypedValue.applyDimension(
95 | TypedValue.COMPLEX_UNIT_DIP, TEXT_SIZE_DIP, getResources().getDisplayMetrics());
96 | borderedText = new BorderedText(textSizePx);
97 | borderedText.setTypeface(Typeface.MONOSPACE);
98 |
99 | tracker = new MultiBoxTracker(this);
100 |
101 | previewWidth = size.getWidth();
102 | previewHeight = size.getHeight();
103 |
104 | LOGGER.i("Initializing at size %dx%d", previewWidth, previewHeight);
105 | rgbFrameBitmap = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
106 | croppedBitmap = Bitmap.createBitmap(CROP_SIZE, CROP_SIZE, Config.ARGB_8888);
107 |
108 | frameToCropTransform =
109 | ImageUtils.getTransformationMatrix(
110 | previewWidth, previewHeight,
111 | CROP_SIZE, CROP_SIZE,
112 | sensorOrientation, false);
113 |
114 | cropToFrameTransform = new Matrix();
115 | frameToCropTransform.invert(cropToFrameTransform);
116 |
117 | trackingOverlay = findViewById(R.id.tracking_overlay);
118 | trackingOverlay.addCallback(
119 | canvas -> {
120 | tracker.draw(canvas);
121 | if (isDebug()) {
122 | tracker.drawDebug(canvas);
123 | }
124 | });
125 |
126 | addCallback(
127 | canvas -> {
128 | if (!isDebug()) {
129 | return;
130 | }
131 | final Bitmap copy = cropCopyBitmap;
132 | if (copy == null) {
133 | return;
134 | }
135 |
136 | final int backgroundColor = Color.argb(100, 0, 0, 0);
137 | canvas.drawColor(backgroundColor);
138 |
139 | final Matrix matrix = new Matrix();
140 | final float scaleFactor = 2;
141 | matrix.postScale(scaleFactor, scaleFactor);
142 | matrix.postTranslate(
143 | canvas.getWidth() - copy.getWidth() * scaleFactor,
144 | canvas.getHeight() - copy.getHeight() * scaleFactor);
145 | canvas.drawBitmap(copy, matrix, new Paint());
146 |
147 | final Vector lines = new Vector();
148 | lines.add("Frame: " + previewWidth + "x" + previewHeight);
149 | lines.add("Crop: " + copy.getWidth() + "x" + copy.getHeight());
150 | lines.add("View: " + canvas.getWidth() + "x" + canvas.getHeight());
151 | lines.add("Rotation: " + sensorOrientation);
152 | lines.add("Inference time: " + lastProcessingTimeMs + "ms");
153 |
154 | borderedText.drawLines(canvas, 10, canvas.getHeight() - 10, lines);
155 | });
156 | }
157 |
158 | OverlayView trackingOverlay;
159 |
160 | void init() {
161 | runInBackground(() -> {
162 | runOnUiThread(()->initSnackbar.show());
163 | try {
164 | deeplab = Deeplab.create(getAssets(), CROP_SIZE, CROP_SIZE, sensorOrientation);
165 | } catch (Exception e) {
166 | LOGGER.e("Exception initializing classifier!", e);
167 | finish();
168 | }
169 | runOnUiThread(()->initSnackbar.dismiss());
170 | initialized = true;
171 | });
172 | }
173 |
174 | @Override
175 | protected void processImage() {
176 | ++timestamp;
177 | final long currTimestamp = timestamp;
178 | byte[] originalLuminance = getLuminance();
179 | tracker.onFrame(
180 | previewWidth,
181 | previewHeight,
182 | getLuminanceStride(),
183 | sensorOrientation,
184 | originalLuminance,
185 | timestamp);
186 | trackingOverlay.postInvalidate();
187 |
188 | // No mutex needed as this method is not reentrant.
189 | if (computingDetection || !initialized) {
190 | readyForNextImage();
191 | return;
192 | }
193 | computingDetection = true;
194 | LOGGER.i("Preparing image " + currTimestamp + " for detection in bg thread.");
195 |
196 | rgbFrameBitmap.setPixels(getRgbBytes(), 0, previewWidth, 0, 0, previewWidth, previewHeight);
197 |
198 | if (luminanceCopy == null) {
199 | luminanceCopy = new byte[originalLuminance.length];
200 | }
201 | System.arraycopy(originalLuminance, 0, luminanceCopy, 0, originalLuminance.length);
202 | readyForNextImage();
203 |
204 | final Canvas canvas = new Canvas(croppedBitmap);
205 | canvas.drawBitmap(rgbFrameBitmap, frameToCropTransform, null);
206 | // For examining the actual TF input.
207 | if (SAVE_PREVIEW_BITMAP) {
208 | ImageUtils.saveBitmap(croppedBitmap);
209 | }
210 |
211 | runInBackground(
212 | () -> {
213 | LOGGER.i("Running detection on image " + currTimestamp);
214 | final long startTime = SystemClock.uptimeMillis();
215 |
216 | cropCopyBitmap = Bitmap.createBitmap(croppedBitmap);
217 | List mappedRecognitions =
218 | deeplab.segment(croppedBitmap,cropToFrameTransform);
219 |
220 | lastProcessingTimeMs = SystemClock.uptimeMillis() - startTime;
221 | tracker.trackResults(mappedRecognitions, luminanceCopy, currTimestamp);
222 | trackingOverlay.postInvalidate();
223 |
224 | requestRender();
225 | computingDetection = false;
226 | });
227 | }
228 |
229 | @Override
230 | protected int getLayoutId() {
231 | return R.layout.camera_connection_fragment_tracking;
232 | }
233 |
234 | @Override
235 | protected Size getDesiredPreviewFrameSize() {
236 | return DESIRED_PREVIEW_SIZE;
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/app/src/main/java/pp/imagesegmenter/OverlayView.java:
--------------------------------------------------------------------------------
1 | /* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | package pp.imagesegmenter;
17 |
18 | import android.content.Context;
19 | import android.graphics.Canvas;
20 | import android.util.AttributeSet;
21 | import android.view.View;
22 | import java.util.LinkedList;
23 | import java.util.List;
24 |
25 | /**
26 | * A simple View providing a render callback to other classes.
27 | */
28 | public class OverlayView extends View {
29 | private final List callbacks = new LinkedList();
30 |
31 | public OverlayView(final Context context, final AttributeSet attrs) {
32 | super(context, attrs);
33 | }
34 |
35 | /**
36 | * Interface defining the callback for client classes.
37 | */
38 | public interface DrawCallback {
39 | public void drawCallback(final Canvas canvas);
40 | }
41 |
42 | public void addCallback(final DrawCallback callback) {
43 | callbacks.add(callback);
44 | }
45 |
46 | @Override
47 | public synchronized void draw(final Canvas canvas) {
48 | for (final DrawCallback callback : callbacks) {
49 | callback.drawCallback(canvas);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/pp/imagesegmenter/env/BorderedText.java:
--------------------------------------------------------------------------------
1 | /* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | package pp.imagesegmenter.env;
17 |
18 | import android.graphics.Canvas;
19 | import android.graphics.Color;
20 | import android.graphics.Paint;
21 | import android.graphics.Paint.Align;
22 | import android.graphics.Paint.Style;
23 | import android.graphics.Rect;
24 | import android.graphics.Typeface;
25 |
26 | import java.util.Vector;
27 |
28 | /**
29 | * A class that encapsulates the tedious bits of rendering legible, bordered text onto a canvas.
30 | */
31 | public class BorderedText {
32 | private final Paint interiorPaint;
33 | private final Paint exteriorPaint;
34 |
35 | private final float textSize;
36 |
37 | /**
38 | * Creates a left-aligned bordered text object with a white interior, and a black exterior with
39 | * the specified text size.
40 | *
41 | * @param textSize text size in pixels
42 | */
43 | public BorderedText(final float textSize) {
44 | this(Color.WHITE, Color.BLACK, textSize);
45 | }
46 |
47 | /**
48 | * Create a bordered text object with the specified interior and exterior colors, text size and
49 | * alignment.
50 | *
51 | * @param interiorColor the interior text color
52 | * @param exteriorColor the exterior text color
53 | * @param textSize text size in pixels
54 | */
55 | public BorderedText(final int interiorColor, final int exteriorColor, final float textSize) {
56 | interiorPaint = new Paint();
57 | interiorPaint.setTextSize(textSize);
58 | interiorPaint.setColor(interiorColor);
59 | interiorPaint.setStyle(Style.FILL);
60 | interiorPaint.setAntiAlias(false);
61 | interiorPaint.setAlpha(255);
62 |
63 | exteriorPaint = new Paint();
64 | exteriorPaint.setTextSize(textSize);
65 | exteriorPaint.setColor(exteriorColor);
66 | exteriorPaint.setStyle(Style.FILL_AND_STROKE);
67 | exteriorPaint.setStrokeWidth(textSize / 8);
68 | exteriorPaint.setAntiAlias(false);
69 | exteriorPaint.setAlpha(255);
70 |
71 | this.textSize = textSize;
72 | }
73 |
74 | public void setTypeface(Typeface typeface) {
75 | interiorPaint.setTypeface(typeface);
76 | exteriorPaint.setTypeface(typeface);
77 | }
78 |
79 | public void drawText(final Canvas canvas, final float posX, final float posY, final String text) {
80 | canvas.drawText(text, posX, posY, exteriorPaint);
81 | canvas.drawText(text, posX, posY, interiorPaint);
82 | }
83 |
84 | public void drawLines(Canvas canvas, final float posX, final float posY, Vector lines) {
85 | int lineNum = 0;
86 | for (final String line : lines) {
87 | drawText(canvas, posX, posY - getTextSize() * (lines.size() - lineNum - 1), line);
88 | ++lineNum;
89 | }
90 | }
91 |
92 | public void setInteriorColor(final int color) {
93 | interiorPaint.setColor(color);
94 | }
95 |
96 | public void setExteriorColor(final int color) {
97 | exteriorPaint.setColor(color);
98 | }
99 |
100 | public float getTextSize() {
101 | return textSize;
102 | }
103 |
104 | public void setAlpha(final int alpha) {
105 | interiorPaint.setAlpha(alpha);
106 | exteriorPaint.setAlpha(alpha);
107 | }
108 |
109 | public void getTextBounds(
110 | final String line, final int index, final int count, final Rect lineBounds) {
111 | interiorPaint.getTextBounds(line, index, count, lineBounds);
112 | }
113 |
114 | public void setTextAlign(final Align align) {
115 | interiorPaint.setTextAlign(align);
116 | exteriorPaint.setTextAlign(align);
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/app/src/main/java/pp/imagesegmenter/env/Logger.java:
--------------------------------------------------------------------------------
1 | /* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | package pp.imagesegmenter.env;
17 |
18 | import android.util.Log;
19 |
20 | import java.util.HashSet;
21 | import java.util.Set;
22 |
23 | /**
24 | * Wrapper for the platform log function, allows convenient message prefixing and log disabling.
25 | */
26 | public final class Logger {
27 | private static final String DEFAULT_TAG = "imagesegmenter";
28 | private static final int DEFAULT_MIN_LOG_LEVEL = Log.DEBUG;
29 |
30 | // Classes to be ignored when examining the stack trace
31 | private static final Set IGNORED_CLASS_NAMES;
32 |
33 | static {
34 | IGNORED_CLASS_NAMES = new HashSet<>(3);
35 | IGNORED_CLASS_NAMES.add("dalvik.system.VMStack");
36 | IGNORED_CLASS_NAMES.add("java.lang.Thread");
37 | IGNORED_CLASS_NAMES.add(Logger.class.getCanonicalName());
38 | }
39 |
40 | private final String tag;
41 | private final String messagePrefix;
42 | private int minLogLevel = DEFAULT_MIN_LOG_LEVEL;
43 |
44 | /**
45 | * Creates a Logger using the class name as the message prefix.
46 | *
47 | * @param clazz the simple name of this class is used as the message prefix.
48 | */
49 | public Logger(final Class> clazz) {
50 | this(clazz.getSimpleName());
51 | }
52 |
53 | /**
54 | * Creates a Logger using the specified message prefix.
55 | *
56 | * @param messagePrefix is prepended to the text of every message.
57 | */
58 | public Logger(final String messagePrefix) {
59 | this(DEFAULT_TAG, messagePrefix);
60 | }
61 |
62 | /**
63 | * Creates a Logger with a custom tag and a custom message prefix. If the message prefix
64 | * is set to null
, the caller's class name is used as the prefix.
65 | *
66 | * @param tag identifies the source of a log message.
67 | * @param messagePrefix prepended to every message if non-null. If null, the name of the caller is
68 | * being used
69 | */
70 | public Logger(final String tag, final String messagePrefix) {
71 | this.tag = tag;
72 | final String prefix = messagePrefix == null ? getCallerSimpleName() : messagePrefix;
73 | this.messagePrefix = (prefix.length() > 0) ? prefix + ": " : prefix;
74 | }
75 |
76 | /**
77 | * Creates a Logger using the caller's class name as the message prefix.
78 | */
79 | public Logger() {
80 | this(DEFAULT_TAG, null);
81 | }
82 |
83 | /**
84 | * Creates a Logger using the caller's class name as the message prefix.
85 | */
86 | public Logger(final int minLogLevel) {
87 | this(DEFAULT_TAG, null);
88 | this.minLogLevel = minLogLevel;
89 | }
90 |
91 | public void setMinLogLevel(final int minLogLevel) {
92 | this.minLogLevel = minLogLevel;
93 | }
94 |
95 | public boolean isLoggable(final int logLevel) {
96 | return logLevel >= minLogLevel || Log.isLoggable(tag, logLevel);
97 | }
98 |
99 | /**
100 | * Return caller's simple name.
101 | *
102 | * Android getStackTrace() returns an array that looks like this:
103 | * stackTrace[0]: dalvik.system.VMStack
104 | * stackTrace[1]: java.lang.Thread
105 | * stackTrace[2]: com.google.android.apps.unveil.env.UnveilLogger
106 | * stackTrace[3]: com.google.android.apps.unveil.BaseApplication
107 | *
108 | * This function returns the simple version of the first non-filtered name.
109 | *
110 | * @return caller's simple name
111 | */
112 | private static String getCallerSimpleName() {
113 | // Get the current callstack so we can pull the class of the caller off of it.
114 | final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
115 |
116 | for (final StackTraceElement elem : stackTrace) {
117 | final String className = elem.getClassName();
118 | if (!IGNORED_CLASS_NAMES.contains(className)) {
119 | // We're only interested in the simple name of the class, not the complete package.
120 | final String[] classParts = className.split("\\.");
121 | return classParts[classParts.length - 1];
122 | }
123 | }
124 |
125 | return Logger.class.getSimpleName();
126 | }
127 |
128 | private String toMessage(final String format, final Object... args) {
129 | return messagePrefix + (args.length > 0 ? String.format(format, args) : format);
130 | }
131 |
132 | public void v(final String format, final Object... args) {
133 | if (isLoggable(Log.VERBOSE)) {
134 | Log.v(tag, toMessage(format, args));
135 | }
136 | }
137 |
138 | public void v(final Throwable t, final String format, final Object... args) {
139 | if (isLoggable(Log.VERBOSE)) {
140 | Log.v(tag, toMessage(format, args), t);
141 | }
142 | }
143 |
144 | public void d(final String format, final Object... args) {
145 | if (isLoggable(Log.DEBUG)) {
146 | Log.d(tag, toMessage(format, args));
147 | }
148 | }
149 |
150 | public void d(final Throwable t, final String format, final Object... args) {
151 | if (isLoggable(Log.DEBUG)) {
152 | Log.d(tag, toMessage(format, args), t);
153 | }
154 | }
155 |
156 | public void i(final String format, final Object... args) {
157 | if (isLoggable(Log.INFO)) {
158 | Log.i(tag, toMessage(format, args));
159 | }
160 | }
161 |
162 | public void i(final Throwable t, final String format, final Object... args) {
163 | if (isLoggable(Log.INFO)) {
164 | Log.i(tag, toMessage(format, args), t);
165 | }
166 | }
167 |
168 | public void w(final String format, final Object... args) {
169 | if (isLoggable(Log.WARN)) {
170 | Log.w(tag, toMessage(format, args));
171 | }
172 | }
173 |
174 | public void w(final Throwable t, final String format, final Object... args) {
175 | if (isLoggable(Log.WARN)) {
176 | Log.w(tag, toMessage(format, args), t);
177 | }
178 | }
179 |
180 | public void e(final String format, final Object... args) {
181 | if (isLoggable(Log.ERROR)) {
182 | Log.e(tag, toMessage(format, args));
183 | }
184 | }
185 |
186 | public void e(final Throwable t, final String format, final Object... args) {
187 | if (isLoggable(Log.ERROR)) {
188 | Log.e(tag, toMessage(format, args), t);
189 | }
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/app/src/main/java/pp/imagesegmenter/env/Size.java:
--------------------------------------------------------------------------------
1 | /* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | package pp.imagesegmenter.env;
17 |
18 | import android.graphics.Bitmap;
19 | import android.text.TextUtils;
20 | import java.io.Serializable;
21 | import java.util.ArrayList;
22 | import java.util.List;
23 |
24 | /**
25 | * Size class independent of a Camera object.
26 | */
27 | public class Size implements Comparable, Serializable {
28 |
29 | // 1.4 went out with this UID so we'll need to maintain it to preserve pending queries when
30 | // upgrading.
31 | public static final long serialVersionUID = 7689808733290872361L;
32 |
33 | public final int width;
34 | public final int height;
35 |
36 | public Size(final int width, final int height) {
37 | this.width = width;
38 | this.height = height;
39 | }
40 |
41 | public Size(final Bitmap bmp) {
42 | this.width = bmp.getWidth();
43 | this.height = bmp.getHeight();
44 | }
45 |
46 | /**
47 | * Rotate a size by the given number of degrees.
48 | * @param size Size to rotate.
49 | * @param rotation Degrees {0, 90, 180, 270} to rotate the size.
50 | * @return Rotated size.
51 | */
52 | public static Size getRotatedSize(final Size size, final int rotation) {
53 | if (rotation % 180 != 0) {
54 | // The phone is portrait, therefore the camera is sideways and frame should be rotated.
55 | return new Size(size.height, size.width);
56 | }
57 | return size;
58 | }
59 |
60 | public static Size parseFromString(String sizeString) {
61 | if (TextUtils.isEmpty(sizeString)) {
62 | return null;
63 | }
64 |
65 | sizeString = sizeString.trim();
66 |
67 | // The expected format is "x".
68 | final String[] components = sizeString.split("x");
69 | if (components.length == 2) {
70 | try {
71 | final int width = Integer.parseInt(components[0]);
72 | final int height = Integer.parseInt(components[1]);
73 | return new Size(width, height);
74 | } catch (final NumberFormatException e) {
75 | return null;
76 | }
77 | } else {
78 | return null;
79 | }
80 | }
81 |
82 | public static List sizeStringToList(final String sizes) {
83 | final List sizeList = new ArrayList();
84 | if (sizes != null) {
85 | final String[] pairs = sizes.split(",");
86 | for (final String pair : pairs) {
87 | final Size size = Size.parseFromString(pair);
88 | if (size != null) {
89 | sizeList.add(size);
90 | }
91 | }
92 | }
93 | return sizeList;
94 | }
95 |
96 | public static String sizeListToString(final List sizes) {
97 | String sizesString = "";
98 | if (sizes != null && sizes.size() > 0) {
99 | sizesString = sizes.get(0).toString();
100 | for (int i = 1; i < sizes.size(); i++) {
101 | sizesString += "," + sizes.get(i).toString();
102 | }
103 | }
104 | return sizesString;
105 | }
106 |
107 | public final float aspectRatio() {
108 | return (float) width / (float) height;
109 | }
110 |
111 | @Override
112 | public int compareTo(final Size other) {
113 | return width * height - other.width * other.height;
114 | }
115 |
116 | @Override
117 | public boolean equals(final Object other) {
118 | if (other == null) {
119 | return false;
120 | }
121 |
122 | if (!(other instanceof Size)) {
123 | return false;
124 | }
125 |
126 | final Size otherSize = (Size) other;
127 | return (width == otherSize.width && height == otherSize.height);
128 | }
129 |
130 | @Override
131 | public int hashCode() {
132 | return width * 32713 + height;
133 | }
134 |
135 | @Override
136 | public String toString() {
137 | return dimensionsAsString(width, height);
138 | }
139 |
140 | public static final String dimensionsAsString(final int width, final int height) {
141 | return width + "x" + height;
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/app/src/main/jni/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (C) 2016 The Android Open Source Project
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | cmake_minimum_required(VERSION 3.4.1)
18 | set(CMAKE_VERBOSE_MAKEFILE on)
19 |
20 | get_filename_component(SRC_DIR ${CMAKE_SOURCE_DIR}/.. ABSOLUTE)
21 |
22 |
23 | project(TENSORFLOW_DEMO)
24 |
25 | if (ANDROID_ABI MATCHES "^armeabi-v7a$")
26 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfloat-abi=softfp -mfpu=neon")
27 | elseif(ANDROID_ABI MATCHES "^arm64-v8a")
28 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -ftree-vectorize")
29 | endif()
30 |
31 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTANDALONE_DEMO_LIB \
32 | -std=c++11 -fno-exceptions -fno-rtti -O2 -Wno-narrowing \
33 | -fPIE")
34 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} \
35 | -Wl,--allow-multiple-definition \
36 | -Wl,--whole-archive -fPIE -v")
37 |
38 | file(GLOB_RECURSE tensorflow_demo_sources ${SRC_DIR}/jni/*.*)
39 | add_library(tensorflow_demo SHARED
40 | ${tensorflow_demo_sources})
41 | target_include_directories(tensorflow_demo PRIVATE
42 | ${CMAKE_SOURCE_DIR})
43 |
44 | target_link_libraries(tensorflow_demo
45 | android
46 | log
47 | jnigraphics
48 | m
49 | atomic
50 | z)
51 |
--------------------------------------------------------------------------------
/app/src/main/jni/imageutils_jni.cc:
--------------------------------------------------------------------------------
1 | /* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | // This file binds the native image utility code to the Java class
17 | // which exposes them.
18 |
19 | #include
20 | #include
21 | #include
22 |
23 | #include "rgb2yuv.h"
24 | #include "yuv2rgb.h"
25 |
26 | #define IMAGEUTILS_METHOD(METHOD_NAME) \
27 | Java_pp_imagesegmenter_env_ImageUtils_##METHOD_NAME // NOLINT
28 |
29 | #ifdef __cplusplus
30 | extern "C" {
31 | #endif
32 |
33 | JNIEXPORT void JNICALL
34 | IMAGEUTILS_METHOD(convertYUV420SPToARGB8888)(
35 | JNIEnv* env, jclass clazz, jbyteArray input, jintArray output,
36 | jint width, jint height, jboolean halfSize);
37 |
38 | JNIEXPORT void JNICALL IMAGEUTILS_METHOD(convertYUV420ToARGB8888)(
39 | JNIEnv* env, jclass clazz, jbyteArray y, jbyteArray u, jbyteArray v,
40 | jintArray output, jint width, jint height, jint y_row_stride,
41 | jint uv_row_stride, jint uv_pixel_stride, jboolean halfSize);
42 |
43 | JNIEXPORT void JNICALL IMAGEUTILS_METHOD(convertYUV420SPToRGB565)(
44 | JNIEnv* env, jclass clazz, jbyteArray input, jbyteArray output, jint width,
45 | jint height);
46 |
47 | JNIEXPORT void JNICALL
48 | IMAGEUTILS_METHOD(convertARGB8888ToYUV420SP)(
49 | JNIEnv* env, jclass clazz, jintArray input, jbyteArray output,
50 | jint width, jint height);
51 |
52 | JNIEXPORT void JNICALL
53 | IMAGEUTILS_METHOD(convertRGB565ToYUV420SP)(
54 | JNIEnv* env, jclass clazz, jbyteArray input, jbyteArray output,
55 | jint width, jint height);
56 |
57 | #ifdef __cplusplus
58 | }
59 | #endif
60 |
61 | JNIEXPORT void JNICALL
62 | IMAGEUTILS_METHOD(convertYUV420SPToARGB8888)(
63 | JNIEnv* env, jclass clazz, jbyteArray input, jintArray output,
64 | jint width, jint height, jboolean halfSize) {
65 | jboolean inputCopy = JNI_FALSE;
66 | jbyte* const i = env->GetByteArrayElements(input, &inputCopy);
67 |
68 | jboolean outputCopy = JNI_FALSE;
69 | jint* const o = env->GetIntArrayElements(output, &outputCopy);
70 |
71 | if (halfSize) {
72 | ConvertYUV420SPToARGB8888HalfSize(reinterpret_cast(i),
73 | reinterpret_cast(o), width,
74 | height);
75 | } else {
76 | ConvertYUV420SPToARGB8888(reinterpret_cast(i),
77 | reinterpret_cast(i) + width * height,
78 | reinterpret_cast(o), width, height);
79 | }
80 |
81 | env->ReleaseByteArrayElements(input, i, JNI_ABORT);
82 | env->ReleaseIntArrayElements(output, o, 0);
83 | }
84 |
85 | JNIEXPORT void JNICALL IMAGEUTILS_METHOD(convertYUV420ToARGB8888)(
86 | JNIEnv* env, jclass clazz, jbyteArray y, jbyteArray u, jbyteArray v,
87 | jintArray output, jint width, jint height, jint y_row_stride,
88 | jint uv_row_stride, jint uv_pixel_stride, jboolean halfSize) {
89 | jboolean inputCopy = JNI_FALSE;
90 | jbyte* const y_buff = env->GetByteArrayElements(y, &inputCopy);
91 | jboolean outputCopy = JNI_FALSE;
92 | jint* const o = env->GetIntArrayElements(output, &outputCopy);
93 |
94 | if (halfSize) {
95 | ConvertYUV420SPToARGB8888HalfSize(reinterpret_cast(y_buff),
96 | reinterpret_cast(o), width,
97 | height);
98 | } else {
99 | jbyte* const u_buff = env->GetByteArrayElements(u, &inputCopy);
100 | jbyte* const v_buff = env->GetByteArrayElements(v, &inputCopy);
101 |
102 | ConvertYUV420ToARGB8888(
103 | reinterpret_cast(y_buff), reinterpret_cast(u_buff),
104 | reinterpret_cast(v_buff), reinterpret_cast(o),
105 | width, height, y_row_stride, uv_row_stride, uv_pixel_stride);
106 |
107 | env->ReleaseByteArrayElements(u, u_buff, JNI_ABORT);
108 | env->ReleaseByteArrayElements(v, v_buff, JNI_ABORT);
109 | }
110 |
111 | env->ReleaseByteArrayElements(y, y_buff, JNI_ABORT);
112 | env->ReleaseIntArrayElements(output, o, 0);
113 | }
114 |
115 | JNIEXPORT void JNICALL IMAGEUTILS_METHOD(convertYUV420SPToRGB565)(
116 | JNIEnv* env, jclass clazz, jbyteArray input, jbyteArray output, jint width,
117 | jint height) {
118 | jboolean inputCopy = JNI_FALSE;
119 | jbyte* const i = env->GetByteArrayElements(input, &inputCopy);
120 |
121 | jboolean outputCopy = JNI_FALSE;
122 | jbyte* const o = env->GetByteArrayElements(output, &outputCopy);
123 |
124 | ConvertYUV420SPToRGB565(reinterpret_cast(i),
125 | reinterpret_cast(o), width, height);
126 |
127 | env->ReleaseByteArrayElements(input, i, JNI_ABORT);
128 | env->ReleaseByteArrayElements(output, o, 0);
129 | }
130 |
131 | JNIEXPORT void JNICALL
132 | IMAGEUTILS_METHOD(convertARGB8888ToYUV420SP)(
133 | JNIEnv* env, jclass clazz, jintArray input, jbyteArray output,
134 | jint width, jint height) {
135 | jboolean inputCopy = JNI_FALSE;
136 | jint* const i = env->GetIntArrayElements(input, &inputCopy);
137 |
138 | jboolean outputCopy = JNI_FALSE;
139 | jbyte* const o = env->GetByteArrayElements(output, &outputCopy);
140 |
141 | ConvertARGB8888ToYUV420SP(reinterpret_cast(i),
142 | reinterpret_cast(o), width, height);
143 |
144 | env->ReleaseIntArrayElements(input, i, JNI_ABORT);
145 | env->ReleaseByteArrayElements(output, o, 0);
146 | }
147 |
148 | JNIEXPORT void JNICALL
149 | IMAGEUTILS_METHOD(convertRGB565ToYUV420SP)(
150 | JNIEnv* env, jclass clazz, jbyteArray input, jbyteArray output,
151 | jint width, jint height) {
152 | jboolean inputCopy = JNI_FALSE;
153 | jbyte* const i = env->GetByteArrayElements(input, &inputCopy);
154 |
155 | jboolean outputCopy = JNI_FALSE;
156 | jbyte* const o = env->GetByteArrayElements(output, &outputCopy);
157 |
158 | ConvertRGB565ToYUV420SP(reinterpret_cast(i),
159 | reinterpret_cast(o), width, height);
160 |
161 | env->ReleaseByteArrayElements(input, i, JNI_ABORT);
162 | env->ReleaseByteArrayElements(output, o, 0);
163 | }
164 |
--------------------------------------------------------------------------------
/app/src/main/jni/object_tracking/frame_pair.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | #ifndef TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_FRAME_PAIR_H_
17 | #define TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_FRAME_PAIR_H_
18 |
19 | #include "keypoint.h"
20 |
21 | namespace tf_tracking {
22 |
23 | // A class that records keypoint correspondences from pairs of
24 | // consecutive frames.
25 | class FramePair {
26 | public:
27 | FramePair()
28 | : start_time_(0),
29 | end_time_(0),
30 | number_of_keypoints_(0) {}
31 |
32 | // Cleans up the FramePair so that they can be reused.
33 | void Init(const int64_t start_time, const int64_t end_time);
34 |
35 | void AdjustBox(const BoundingBox box,
36 | float* const translation_x,
37 | float* const translation_y,
38 | float* const scale_x,
39 | float* const scale_y) const;
40 |
41 | private:
42 | // Returns the weighted median of the given deltas, computed independently on
43 | // x and y. Returns 0,0 in case of failure. The assumption is that a
44 | // translation of 0.0 in the degenerate case is the best that can be done, and
45 | // should not be considered an error.
46 | //
47 | // In the case of scale, a slight exception is made just to be safe and
48 | // there is a check for 0.0 explicitly, but that shouldn't ever be possible to
49 | // happen naturally because of the non-zero + parity checks in FillScales.
50 | Point2f GetWeightedMedian(const float* const weights,
51 | const Point2f* const deltas) const;
52 |
53 | float GetWeightedMedianScale(const float* const weights,
54 | const Point2f* const deltas) const;
55 |
56 | // Weights points based on the query_point and cutoff_dist.
57 | int FillWeights(const BoundingBox& box,
58 | float* const weights) const;
59 |
60 | // Fills in the array of deltas with the translations of the points
61 | // between frames.
62 | void FillTranslations(Point2f* const translations) const;
63 |
64 | // Fills in the array of deltas with the relative scale factor of points
65 | // relative to a given center. Has the ability to override the weight to 0 if
66 | // a degenerate scale is detected.
67 | // Translation is the amount the center of the box has moved from one frame to
68 | // the next.
69 | int FillScales(const Point2f& old_center,
70 | const Point2f& translation,
71 | float* const weights,
72 | Point2f* const scales) const;
73 |
74 | // TODO(andrewharp): Make these private.
75 | public:
76 | // The time at frame1.
77 | int64_t start_time_;
78 |
79 | // The time at frame2.
80 | int64_t end_time_;
81 |
82 | // This array will contain the keypoints found in frame 1.
83 | Keypoint frame1_keypoints_[kMaxKeypoints];
84 |
85 | // Contain the locations of the keypoints from frame 1 in frame 2.
86 | Keypoint frame2_keypoints_[kMaxKeypoints];
87 |
88 | // The number of keypoints in frame 1.
89 | int number_of_keypoints_;
90 |
91 | // Keeps track of which keypoint correspondences were actually found from one
92 | // frame to another.
93 | // The i-th element of this array will be non-zero if and only if the i-th
94 | // keypoint of frame 1 was found in frame 2.
95 | bool optical_flow_found_keypoint_[kMaxKeypoints];
96 |
97 | private:
98 | TF_DISALLOW_COPY_AND_ASSIGN(FramePair);
99 | };
100 |
101 | } // namespace tf_tracking
102 |
103 | #endif // TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_FRAME_PAIR_H_
104 |
--------------------------------------------------------------------------------
/app/src/main/jni/object_tracking/geom.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | #ifndef TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_GEOM_H_
17 | #define TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_GEOM_H_
18 |
19 | #include
20 | #include "utils.h"
21 |
22 | namespace tf_tracking {
23 |
24 | struct Size {
25 | Size(const int width, const int height) : width(width), height(height) {}
26 |
27 | int width;
28 | int height;
29 | };
30 |
31 |
32 | class Point2f {
33 | public:
34 | Point2f() : x(0.0f), y(0.0f) {}
35 | Point2f(const float x, const float y) : x(x), y(y) {}
36 |
37 | inline Point2f operator- (const Point2f& that) const {
38 | return Point2f(this->x - that.x, this->y - that.y);
39 | }
40 |
41 | inline Point2f operator+ (const Point2f& that) const {
42 | return Point2f(this->x + that.x, this->y + that.y);
43 | }
44 |
45 | inline Point2f& operator+= (const Point2f& that) {
46 | this->x += that.x;
47 | this->y += that.y;
48 | return *this;
49 | }
50 |
51 | inline Point2f& operator-= (const Point2f& that) {
52 | this->x -= that.x;
53 | this->y -= that.y;
54 | return *this;
55 | }
56 |
57 | inline Point2f operator- (const Point2f& that) {
58 | return Point2f(this->x - that.x, this->y - that.y);
59 | }
60 |
61 | inline float LengthSquared() {
62 | return Square(this->x) + Square(this->y);
63 | }
64 |
65 | inline float Length() {
66 | return sqrtf(LengthSquared());
67 | }
68 |
69 | inline float DistanceSquared(const Point2f& that) {
70 | return Square(this->x - that.x) + Square(this->y - that.y);
71 | }
72 |
73 | inline float Distance(const Point2f& that) {
74 | return sqrtf(DistanceSquared(that));
75 | }
76 |
77 | float x;
78 | float y;
79 | };
80 |
81 | inline std::ostream& operator<<(std::ostream& stream, const Point2f& point) {
82 | stream << point.x << "," << point.y;
83 | return stream;
84 | }
85 |
86 | class BoundingBox {
87 | public:
88 | BoundingBox()
89 | : left_(0),
90 | top_(0),
91 | right_(0),
92 | bottom_(0) {}
93 |
94 | BoundingBox(const BoundingBox& bounding_box)
95 | : left_(bounding_box.left_),
96 | top_(bounding_box.top_),
97 | right_(bounding_box.right_),
98 | bottom_(bounding_box.bottom_) {
99 | SCHECK(left_ < right_, "Bounds out of whack! %.2f vs %.2f!", left_, right_);
100 | SCHECK(top_ < bottom_, "Bounds out of whack! %.2f vs %.2f!", top_, bottom_);
101 | }
102 |
103 | BoundingBox(const float left,
104 | const float top,
105 | const float right,
106 | const float bottom)
107 | : left_(left),
108 | top_(top),
109 | right_(right),
110 | bottom_(bottom) {
111 | SCHECK(left_ < right_, "Bounds out of whack! %.2f vs %.2f!", left_, right_);
112 | SCHECK(top_ < bottom_, "Bounds out of whack! %.2f vs %.2f!", top_, bottom_);
113 | }
114 |
115 | BoundingBox(const Point2f& point1, const Point2f& point2)
116 | : left_(MIN(point1.x, point2.x)),
117 | top_(MIN(point1.y, point2.y)),
118 | right_(MAX(point1.x, point2.x)),
119 | bottom_(MAX(point1.y, point2.y)) {}
120 |
121 | inline void CopyToArray(float* const bounds_array) const {
122 | bounds_array[0] = left_;
123 | bounds_array[1] = top_;
124 | bounds_array[2] = right_;
125 | bounds_array[3] = bottom_;
126 | }
127 |
128 | inline float GetWidth() const {
129 | return right_ - left_;
130 | }
131 |
132 | inline float GetHeight() const {
133 | return bottom_ - top_;
134 | }
135 |
136 | inline float GetArea() const {
137 | const float width = GetWidth();
138 | const float height = GetHeight();
139 | if (width <= 0 || height <= 0) {
140 | return 0.0f;
141 | }
142 |
143 | return width * height;
144 | }
145 |
146 | inline Point2f GetCenter() const {
147 | return Point2f((left_ + right_) / 2.0f,
148 | (top_ + bottom_) / 2.0f);
149 | }
150 |
151 | inline bool ValidBox() const {
152 | return GetArea() > 0.0f;
153 | }
154 |
155 | // Returns a bounding box created from the overlapping area of these two.
156 | inline BoundingBox Intersect(const BoundingBox& that) const {
157 | const float new_left = MAX(this->left_, that.left_);
158 | const float new_right = MIN(this->right_, that.right_);
159 |
160 | if (new_left >= new_right) {
161 | return BoundingBox();
162 | }
163 |
164 | const float new_top = MAX(this->top_, that.top_);
165 | const float new_bottom = MIN(this->bottom_, that.bottom_);
166 |
167 | if (new_top >= new_bottom) {
168 | return BoundingBox();
169 | }
170 |
171 | return BoundingBox(new_left, new_top, new_right, new_bottom);
172 | }
173 |
174 | // Returns a bounding box that can contain both boxes.
175 | inline BoundingBox Union(const BoundingBox& that) const {
176 | return BoundingBox(MIN(this->left_, that.left_),
177 | MIN(this->top_, that.top_),
178 | MAX(this->right_, that.right_),
179 | MAX(this->bottom_, that.bottom_));
180 | }
181 |
182 | inline float PascalScore(const BoundingBox& that) const {
183 | SCHECK(GetArea() > 0.0f, "Empty bounding box!");
184 | SCHECK(that.GetArea() > 0.0f, "Empty bounding box!");
185 |
186 | const float intersect_area = this->Intersect(that).GetArea();
187 |
188 | if (intersect_area <= 0) {
189 | return 0;
190 | }
191 |
192 | const float score =
193 | intersect_area / (GetArea() + that.GetArea() - intersect_area);
194 | SCHECK(InRange(score, 0.0f, 1.0f), "Invalid score! %.2f", score);
195 | return score;
196 | }
197 |
198 | inline bool Intersects(const BoundingBox& that) const {
199 | return InRange(that.left_, left_, right_)
200 | || InRange(that.right_, left_, right_)
201 | || InRange(that.top_, top_, bottom_)
202 | || InRange(that.bottom_, top_, bottom_);
203 | }
204 |
205 | // Returns whether another bounding box is completely inside of this bounding
206 | // box. Sharing edges is ok.
207 | inline bool Contains(const BoundingBox& that) const {
208 | return that.left_ >= left_ &&
209 | that.right_ <= right_ &&
210 | that.top_ >= top_ &&
211 | that.bottom_ <= bottom_;
212 | }
213 |
214 | inline bool Contains(const Point2f& point) const {
215 | return InRange(point.x, left_, right_) && InRange(point.y, top_, bottom_);
216 | }
217 |
218 | inline void Shift(const Point2f shift_amount) {
219 | left_ += shift_amount.x;
220 | top_ += shift_amount.y;
221 | right_ += shift_amount.x;
222 | bottom_ += shift_amount.y;
223 | }
224 |
225 | inline void ScaleOrigin(const float scale_x, const float scale_y) {
226 | left_ *= scale_x;
227 | right_ *= scale_x;
228 | top_ *= scale_y;
229 | bottom_ *= scale_y;
230 | }
231 |
232 | inline void Scale(const float scale_x, const float scale_y) {
233 | const Point2f center = GetCenter();
234 | const float half_width = GetWidth() / 2.0f;
235 | const float half_height = GetHeight() / 2.0f;
236 |
237 | left_ = center.x - half_width * scale_x;
238 | right_ = center.x + half_width * scale_x;
239 |
240 | top_ = center.y - half_height * scale_y;
241 | bottom_ = center.y + half_height * scale_y;
242 | }
243 |
244 | float left_;
245 | float top_;
246 | float right_;
247 | float bottom_;
248 | };
249 | inline std::ostream& operator<<(std::ostream& stream, const BoundingBox& box) {
250 | stream << "[" << box.left_ << " - " << box.right_
251 | << ", " << box.top_ << " - " << box.bottom_
252 | << ", w:" << box.GetWidth() << " h:" << box.GetHeight() << "]";
253 | return stream;
254 | }
255 |
256 |
257 | class BoundingSquare {
258 | public:
259 | BoundingSquare(const float x, const float y, const float size)
260 | : x_(x), y_(y), size_(size) {}
261 |
262 | explicit BoundingSquare(const BoundingBox& box)
263 | : x_(box.left_), y_(box.top_), size_(box.GetWidth()) {
264 | #ifdef SANITY_CHECKS
265 | if (std::abs(box.GetWidth() - box.GetHeight()) > 0.1f) {
266 | LOG(WARNING) << "This is not a square: " << box << std::endl;
267 | }
268 | #endif
269 | }
270 |
271 | inline BoundingBox ToBoundingBox() const {
272 | return BoundingBox(x_, y_, x_ + size_, y_ + size_);
273 | }
274 |
275 | inline bool ValidBox() {
276 | return size_ > 0.0f;
277 | }
278 |
279 | inline void Shift(const Point2f shift_amount) {
280 | x_ += shift_amount.x;
281 | y_ += shift_amount.y;
282 | }
283 |
284 | inline void Scale(const float scale) {
285 | const float new_size = size_ * scale;
286 | const float position_diff = (new_size - size_) / 2.0f;
287 | x_ -= position_diff;
288 | y_ -= position_diff;
289 | size_ = new_size;
290 | }
291 |
292 | float x_;
293 | float y_;
294 | float size_;
295 | };
296 | inline std::ostream& operator<<(std::ostream& stream,
297 | const BoundingSquare& square) {
298 | stream << "[" << square.x_ << "," << square.y_ << " " << square.size_ << "]";
299 | return stream;
300 | }
301 |
302 |
303 | inline BoundingSquare GetCenteredSquare(const BoundingBox& original_box,
304 | const float size) {
305 | const float width_diff = (original_box.GetWidth() - size) / 2.0f;
306 | const float height_diff = (original_box.GetHeight() - size) / 2.0f;
307 | return BoundingSquare(original_box.left_ + width_diff,
308 | original_box.top_ + height_diff,
309 | size);
310 | }
311 |
312 | inline BoundingSquare GetCenteredSquare(const BoundingBox& original_box) {
313 | return GetCenteredSquare(
314 | original_box, MIN(original_box.GetWidth(), original_box.GetHeight()));
315 | }
316 |
317 | } // namespace tf_tracking
318 |
319 | #endif // TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_GEOM_H_
320 |
--------------------------------------------------------------------------------
/app/src/main/jni/object_tracking/gl_utils.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | #ifndef TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_GL_UTILS_H_
17 | #define TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_GL_UTILS_H_
18 |
19 | #include
20 | #include
21 |
22 | #include "tensorflow/examples/android/jni/object_tracking/geom.h"
23 |
24 | namespace tf_tracking {
25 |
26 | // Draws a box at the given position.
27 | inline static void DrawBox(const BoundingBox& bounding_box) {
28 | const GLfloat line[] = {
29 | bounding_box.left_, bounding_box.bottom_,
30 | bounding_box.left_, bounding_box.top_,
31 | bounding_box.left_, bounding_box.top_,
32 | bounding_box.right_, bounding_box.top_,
33 | bounding_box.right_, bounding_box.top_,
34 | bounding_box.right_, bounding_box.bottom_,
35 | bounding_box.right_, bounding_box.bottom_,
36 | bounding_box.left_, bounding_box.bottom_
37 | };
38 |
39 | glVertexPointer(2, GL_FLOAT, 0, line);
40 | glEnableClientState(GL_VERTEX_ARRAY);
41 |
42 | glDrawArrays(GL_LINES, 0, 8);
43 | }
44 |
45 |
46 | // Changes the coordinate system such that drawing to an arbitrary square in
47 | // the world can thereafter be drawn to using coordinates 0 - 1.
48 | inline static void MapWorldSquareToUnitSquare(const BoundingSquare& square) {
49 | glScalef(square.size_, square.size_, 1.0f);
50 | glTranslatef(square.x_ / square.size_, square.y_ / square.size_, 0.0f);
51 | }
52 |
53 | } // namespace tf_tracking
54 |
55 | #endif // TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_GL_UTILS_H_
56 |
--------------------------------------------------------------------------------
/app/src/main/jni/object_tracking/image_data.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | #ifndef TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_IMAGE_DATA_H_
17 | #define TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_IMAGE_DATA_H_
18 |
19 | #include
20 | #include
21 |
22 | #include "image-inl.h"
23 | #include "image.h"
24 | #include "image_utils.h"
25 | #include "integral_image.h"
26 | #include "time_log.h"
27 | #include "utils.h"
28 |
29 | #include "config.h"
30 |
31 | namespace tf_tracking {
32 |
33 | // Class that encapsulates all bulky processed data for a frame.
34 | class ImageData {
35 | public:
36 | explicit ImageData(const int width, const int height)
37 | : uv_frame_width_(width << 1),
38 | uv_frame_height_(height << 1),
39 | timestamp_(0),
40 | image_(width, height) {
41 | InitPyramid(width, height);
42 | ResetComputationCache();
43 | }
44 |
45 | private:
46 | void ResetComputationCache() {
47 | uv_data_computed_ = false;
48 | integral_image_computed_ = false;
49 | for (int i = 0; i < kNumPyramidLevels; ++i) {
50 | spatial_x_computed_[i] = false;
51 | spatial_y_computed_[i] = false;
52 | pyramid_sqrt2_computed_[i * 2] = false;
53 | pyramid_sqrt2_computed_[i * 2 + 1] = false;
54 | }
55 | }
56 |
57 | void InitPyramid(const int width, const int height) {
58 | int level_width = width;
59 | int level_height = height;
60 |
61 | for (int i = 0; i < kNumPyramidLevels; ++i) {
62 | pyramid_sqrt2_[i * 2] = NULL;
63 | pyramid_sqrt2_[i * 2 + 1] = NULL;
64 | spatial_x_[i] = NULL;
65 | spatial_y_[i] = NULL;
66 |
67 | level_width /= 2;
68 | level_height /= 2;
69 | }
70 |
71 | // Alias the first pyramid level to image_.
72 | pyramid_sqrt2_[0] = &image_;
73 | }
74 |
75 | public:
76 | ~ImageData() {
77 | // The first pyramid level is actually an alias to image_,
78 | // so make sure it doesn't get deleted here.
79 | pyramid_sqrt2_[0] = NULL;
80 |
81 | for (int i = 0; i < kNumPyramidLevels; ++i) {
82 | SAFE_DELETE(pyramid_sqrt2_[i * 2]);
83 | SAFE_DELETE(pyramid_sqrt2_[i * 2 + 1]);
84 | SAFE_DELETE(spatial_x_[i]);
85 | SAFE_DELETE(spatial_y_[i]);
86 | }
87 | }
88 |
89 | void SetData(const uint8_t* const new_frame, const int stride,
90 | const int64_t timestamp, const int downsample_factor) {
91 | SetData(new_frame, NULL, stride, timestamp, downsample_factor);
92 | }
93 |
94 | void SetData(const uint8_t* const new_frame, const uint8_t* const uv_frame,
95 | const int stride, const int64_t timestamp,
96 | const int downsample_factor) {
97 | ResetComputationCache();
98 |
99 | timestamp_ = timestamp;
100 |
101 | TimeLog("SetData!");
102 |
103 | pyramid_sqrt2_[0]->FromArray(new_frame, stride, downsample_factor);
104 | pyramid_sqrt2_computed_[0] = true;
105 | TimeLog("Downsampled image");
106 |
107 | if (uv_frame != NULL) {
108 | if (u_data_.get() == NULL) {
109 | u_data_.reset(new Image(uv_frame_width_, uv_frame_height_));
110 | v_data_.reset(new Image(uv_frame_width_, uv_frame_height_));
111 | }
112 |
113 | GetUV(uv_frame, u_data_.get(), v_data_.get());
114 | uv_data_computed_ = true;
115 | TimeLog("Copied UV data");
116 | } else {
117 | LOGV("No uv data!");
118 | }
119 |
120 | #ifdef LOG_TIME
121 | // If profiling is enabled, precompute here to make it easier to distinguish
122 | // total costs.
123 | Precompute();
124 | #endif
125 | }
126 |
127 | inline const uint64_t GetTimestamp() const { return timestamp_; }
128 |
129 | inline const Image* GetImage() const {
130 | SCHECK(pyramid_sqrt2_computed_[0], "image not set!");
131 | return pyramid_sqrt2_[0];
132 | }
133 |
134 | const Image* GetPyramidSqrt2Level(const int level) const {
135 | if (!pyramid_sqrt2_computed_[level]) {
136 | SCHECK(level != 0, "Level equals 0!");
137 | if (level == 1) {
138 | const Image& upper_level = *GetPyramidSqrt2Level(0);
139 | if (pyramid_sqrt2_[level] == NULL) {
140 | const int new_width =
141 | (static_cast(upper_level.GetWidth() / sqrtf(2)) + 1) / 2 * 2;
142 | const int new_height =
143 | (static_cast(upper_level.GetHeight() / sqrtf(2)) + 1) / 2 *
144 | 2;
145 |
146 | pyramid_sqrt2_[level] = new Image(new_width, new_height);
147 | }
148 | pyramid_sqrt2_[level]->DownsampleInterpolateLinear(upper_level);
149 | } else {
150 | const Image& upper_level = *GetPyramidSqrt2Level(level - 2);
151 | if (pyramid_sqrt2_[level] == NULL) {
152 | pyramid_sqrt2_[level] = new Image(
153 | upper_level.GetWidth() / 2, upper_level.GetHeight() / 2);
154 | }
155 | pyramid_sqrt2_[level]->DownsampleAveraged(
156 | upper_level.data(), upper_level.stride(), 2);
157 | }
158 | pyramid_sqrt2_computed_[level] = true;
159 | }
160 | return pyramid_sqrt2_[level];
161 | }
162 |
163 | inline const Image* GetSpatialX(const int level) const {
164 | if (!spatial_x_computed_[level]) {
165 | const Image& src = *GetPyramidSqrt2Level(level * 2);
166 | if (spatial_x_[level] == NULL) {
167 | spatial_x_[level] = new Image(src.GetWidth(), src.GetHeight());
168 | }
169 | spatial_x_[level]->DerivativeX(src);
170 | spatial_x_computed_[level] = true;
171 | }
172 | return spatial_x_[level];
173 | }
174 |
175 | inline const Image* GetSpatialY(const int level) const {
176 | if (!spatial_y_computed_[level]) {
177 | const Image& src = *GetPyramidSqrt2Level(level * 2);
178 | if (spatial_y_[level] == NULL) {
179 | spatial_y_[level] = new Image(src.GetWidth(), src.GetHeight());
180 | }
181 | spatial_y_[level]->DerivativeY(src);
182 | spatial_y_computed_[level] = true;
183 | }
184 | return spatial_y_[level];
185 | }
186 |
187 | // The integral image is currently only used for object detection, so lazily
188 | // initialize it on request.
189 | inline const IntegralImage* GetIntegralImage() const {
190 | if (integral_image_.get() == NULL) {
191 | integral_image_.reset(new IntegralImage(image_));
192 | } else if (!integral_image_computed_) {
193 | integral_image_->Recompute(image_);
194 | }
195 | integral_image_computed_ = true;
196 | return integral_image_.get();
197 | }
198 |
199 | inline const Image* GetU() const {
200 | SCHECK(uv_data_computed_, "UV data not provided!");
201 | return u_data_.get();
202 | }
203 |
204 | inline const Image* GetV() const {
205 | SCHECK(uv_data_computed_, "UV data not provided!");
206 | return v_data_.get();
207 | }
208 |
209 | private:
210 | void Precompute() {
211 | // Create the smoothed pyramids.
212 | for (int i = 0; i < kNumPyramidLevels * 2; i += 2) {
213 | (void) GetPyramidSqrt2Level(i);
214 | }
215 | TimeLog("Created smoothed pyramids");
216 |
217 | // Create the smoothed pyramids.
218 | for (int i = 1; i < kNumPyramidLevels * 2; i += 2) {
219 | (void) GetPyramidSqrt2Level(i);
220 | }
221 | TimeLog("Created smoothed sqrt pyramids");
222 |
223 | // Create the spatial derivatives for frame 1.
224 | for (int i = 0; i < kNumPyramidLevels; ++i) {
225 | (void) GetSpatialX(i);
226 | (void) GetSpatialY(i);
227 | }
228 | TimeLog("Created spatial derivatives");
229 |
230 | (void) GetIntegralImage();
231 | TimeLog("Got integral image!");
232 | }
233 |
234 | const int uv_frame_width_;
235 | const int uv_frame_height_;
236 |
237 | int64_t timestamp_;
238 |
239 | Image image_;
240 |
241 | bool uv_data_computed_;
242 | std::unique_ptr > u_data_;
243 | std::unique_ptr > v_data_;
244 |
245 | mutable bool spatial_x_computed_[kNumPyramidLevels];
246 | mutable Image* spatial_x_[kNumPyramidLevels];
247 |
248 | mutable bool spatial_y_computed_[kNumPyramidLevels];
249 | mutable Image* spatial_y_[kNumPyramidLevels];
250 |
251 | // Mutable so the lazy initialization can work when this class is const.
252 | // Whether or not the integral image has been computed for the current image.
253 | mutable bool integral_image_computed_;
254 | mutable std::unique_ptr integral_image_;
255 |
256 | mutable bool pyramid_sqrt2_computed_[kNumPyramidLevels * 2];
257 | mutable Image* pyramid_sqrt2_[kNumPyramidLevels * 2];
258 |
259 | TF_DISALLOW_COPY_AND_ASSIGN(ImageData);
260 | };
261 |
262 | } // namespace tf_tracking
263 |
264 | #endif // TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_IMAGE_DATA_H_
265 |
--------------------------------------------------------------------------------
/app/src/main/jni/object_tracking/image_neon.cc:
--------------------------------------------------------------------------------
1 | /* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | // NEON implementations of Image methods for compatible devices. Control
17 | // should never enter this compilation unit on incompatible devices.
18 |
19 | #ifdef __ARM_NEON
20 |
21 | #include
22 |
23 | #include
24 |
25 | #include "image-inl.h"
26 | #include "image.h"
27 | #include "image_utils.h"
28 | #include "utils.h"
29 |
30 | namespace tf_tracking {
31 |
32 | // This function does the bulk of the work.
33 | template <>
34 | void Image::Downsample2x32ColumnsNeon(const uint8_t* const original,
35 | const int stride,
36 | const int orig_x) {
37 | // Divide input x offset by 2 to find output offset.
38 | const int new_x = orig_x >> 1;
39 |
40 | // Initial offset into top row.
41 | const uint8_t* offset = original + orig_x;
42 |
43 | // This points to the leftmost pixel of our 8 horizontally arranged
44 | // pixels in the destination data.
45 | uint8_t* ptr_dst = (*this)[0] + new_x;
46 |
47 | // Sum along vertical columns.
48 | // Process 32x2 input pixels and 16x1 output pixels per iteration.
49 | for (int new_y = 0; new_y < height_; ++new_y) {
50 | uint16x8_t accum1 = vdupq_n_u16(0);
51 | uint16x8_t accum2 = vdupq_n_u16(0);
52 |
53 | // Go top to bottom across the four rows of input pixels that make up
54 | // this output row.
55 | for (int row_num = 0; row_num < 2; ++row_num) {
56 | // First 16 bytes.
57 | {
58 | // Load 16 bytes of data from current offset.
59 | const uint8x16_t curr_data1 = vld1q_u8(offset);
60 |
61 | // Pairwise add and accumulate into accum vectors (16 bit to account
62 | // for values above 255).
63 | accum1 = vpadalq_u8(accum1, curr_data1);
64 | }
65 |
66 | // Second 16 bytes.
67 | {
68 | // Load 16 bytes of data from current offset.
69 | const uint8x16_t curr_data2 = vld1q_u8(offset + 16);
70 |
71 | // Pairwise add and accumulate into accum vectors (16 bit to account
72 | // for values above 255).
73 | accum2 = vpadalq_u8(accum2, curr_data2);
74 | }
75 |
76 | // Move offset down one row.
77 | offset += stride;
78 | }
79 |
80 | // Divide by 4 (number of input pixels per output
81 | // pixel) and narrow data from 16 bits per pixel to 8 bpp.
82 | const uint8x8_t tmp_pix1 = vqshrn_n_u16(accum1, 2);
83 | const uint8x8_t tmp_pix2 = vqshrn_n_u16(accum2, 2);
84 |
85 | // Concatenate 8x1 pixel strips into 16x1 pixel strip.
86 | const uint8x16_t allpixels = vcombine_u8(tmp_pix1, tmp_pix2);
87 |
88 | // Copy all pixels from composite 16x1 vector into output strip.
89 | vst1q_u8(ptr_dst, allpixels);
90 |
91 | ptr_dst += stride_;
92 | }
93 | }
94 |
95 | // This function does the bulk of the work.
96 | template <>
97 | void Image::Downsample4x32ColumnsNeon(const uint8_t* const original,
98 | const int stride,
99 | const int orig_x) {
100 | // Divide input x offset by 4 to find output offset.
101 | const int new_x = orig_x >> 2;
102 |
103 | // Initial offset into top row.
104 | const uint8_t* offset = original + orig_x;
105 |
106 | // This points to the leftmost pixel of our 8 horizontally arranged
107 | // pixels in the destination data.
108 | uint8_t* ptr_dst = (*this)[0] + new_x;
109 |
110 | // Sum along vertical columns.
111 | // Process 32x4 input pixels and 8x1 output pixels per iteration.
112 | for (int new_y = 0; new_y < height_; ++new_y) {
113 | uint16x8_t accum1 = vdupq_n_u16(0);
114 | uint16x8_t accum2 = vdupq_n_u16(0);
115 |
116 | // Go top to bottom across the four rows of input pixels that make up
117 | // this output row.
118 | for (int row_num = 0; row_num < 4; ++row_num) {
119 | // First 16 bytes.
120 | {
121 | // Load 16 bytes of data from current offset.
122 | const uint8x16_t curr_data1 = vld1q_u8(offset);
123 |
124 | // Pairwise add and accumulate into accum vectors (16 bit to account
125 | // for values above 255).
126 | accum1 = vpadalq_u8(accum1, curr_data1);
127 | }
128 |
129 | // Second 16 bytes.
130 | {
131 | // Load 16 bytes of data from current offset.
132 | const uint8x16_t curr_data2 = vld1q_u8(offset + 16);
133 |
134 | // Pairwise add and accumulate into accum vectors (16 bit to account
135 | // for values above 255).
136 | accum2 = vpadalq_u8(accum2, curr_data2);
137 | }
138 |
139 | // Move offset down one row.
140 | offset += stride;
141 | }
142 |
143 | // Add and widen, then divide by 16 (number of input pixels per output
144 | // pixel) and narrow data from 32 bits per pixel to 16 bpp.
145 | const uint16x4_t tmp_pix1 = vqshrn_n_u32(vpaddlq_u16(accum1), 4);
146 | const uint16x4_t tmp_pix2 = vqshrn_n_u32(vpaddlq_u16(accum2), 4);
147 |
148 | // Combine 4x1 pixel strips into 8x1 pixel strip and narrow from
149 | // 16 bits to 8 bits per pixel.
150 | const uint8x8_t allpixels = vmovn_u16(vcombine_u16(tmp_pix1, tmp_pix2));
151 |
152 | // Copy all pixels from composite 8x1 vector into output strip.
153 | vst1_u8(ptr_dst, allpixels);
154 |
155 | ptr_dst += stride_;
156 | }
157 | }
158 |
159 |
160 | // Hardware accelerated downsampling method for supported devices.
161 | // Requires that image size be a multiple of 16 pixels in each dimension,
162 | // and that downsampling be by a factor of 2 or 4.
163 | template <>
164 | void Image::DownsampleAveragedNeon(const uint8_t* const original,
165 | const int stride,
166 | const int factor) {
167 | // TODO(andrewharp): stride is a bad approximation for the src image's width.
168 | // Better to pass that in directly.
169 | SCHECK(width_ * factor <= stride, "Uh oh!");
170 | const int last_starting_index = width_ * factor - 32;
171 |
172 | // We process 32 input pixels lengthwise at a time.
173 | // The output per pass of this loop is an 8 wide by downsampled height tall
174 | // pixel strip.
175 | int orig_x = 0;
176 | for (; orig_x <= last_starting_index; orig_x += 32) {
177 | if (factor == 2) {
178 | Downsample2x32ColumnsNeon(original, stride, orig_x);
179 | } else {
180 | Downsample4x32ColumnsNeon(original, stride, orig_x);
181 | }
182 | }
183 |
184 | // If a last pass is required, push it to the left enough so that it never
185 | // goes out of bounds. This will result in some extra computation on devices
186 | // whose frame widths are multiples of 16 and not 32.
187 | if (orig_x < last_starting_index + 32) {
188 | if (factor == 2) {
189 | Downsample2x32ColumnsNeon(original, stride, last_starting_index);
190 | } else {
191 | Downsample4x32ColumnsNeon(original, stride, last_starting_index);
192 | }
193 | }
194 | }
195 |
196 |
197 | // Puts the image gradient matrix about a pixel into the 2x2 float array G.
198 | // vals_x should be an array of the window x gradient values, whose indices
199 | // can be in any order but are parallel to the vals_y entries.
200 | // See http://robots.stanford.edu/cs223b04/algo_tracking.pdf for more details.
201 | void CalculateGNeon(const float* const vals_x, const float* const vals_y,
202 | const int num_vals, float* const G) {
203 | const float32_t* const arm_vals_x = (const float32_t*) vals_x;
204 | const float32_t* const arm_vals_y = (const float32_t*) vals_y;
205 |
206 | // Running sums.
207 | float32x4_t xx = vdupq_n_f32(0.0f);
208 | float32x4_t xy = vdupq_n_f32(0.0f);
209 | float32x4_t yy = vdupq_n_f32(0.0f);
210 |
211 | // Maximum index we can load 4 consecutive values from.
212 | // e.g. if there are 81 values, our last full pass can be from index 77:
213 | // 81-4=>77 (77, 78, 79, 80)
214 | const int max_i = num_vals - 4;
215 |
216 | // Defined here because we want to keep track of how many values were
217 | // processed by NEON, so that we can finish off the remainder the normal
218 | // way.
219 | int i = 0;
220 |
221 | // Process values 4 at a time, accumulating the sums of
222 | // the pixel-wise x*x, x*y, and y*y values.
223 | for (; i <= max_i; i += 4) {
224 | // Load xs
225 | float32x4_t x = vld1q_f32(arm_vals_x + i);
226 |
227 | // Multiply x*x and accumulate.
228 | xx = vmlaq_f32(xx, x, x);
229 |
230 | // Load ys
231 | float32x4_t y = vld1q_f32(arm_vals_y + i);
232 |
233 | // Multiply x*y and accumulate.
234 | xy = vmlaq_f32(xy, x, y);
235 |
236 | // Multiply y*y and accumulate.
237 | yy = vmlaq_f32(yy, y, y);
238 | }
239 |
240 | static float32_t xx_vals[4];
241 | static float32_t xy_vals[4];
242 | static float32_t yy_vals[4];
243 |
244 | vst1q_f32(xx_vals, xx);
245 | vst1q_f32(xy_vals, xy);
246 | vst1q_f32(yy_vals, yy);
247 |
248 | // Accumulated values are store in sets of 4, we have to manually add
249 | // the last bits together.
250 | for (int j = 0; j < 4; ++j) {
251 | G[0] += xx_vals[j];
252 | G[1] += xy_vals[j];
253 | G[3] += yy_vals[j];
254 | }
255 |
256 | // Finishes off last few values (< 4) from above.
257 | for (; i < num_vals; ++i) {
258 | G[0] += Square(vals_x[i]);
259 | G[1] += vals_x[i] * vals_y[i];
260 | G[3] += Square(vals_y[i]);
261 | }
262 |
263 | // The matrix is symmetric, so this is a given.
264 | G[2] = G[1];
265 | }
266 |
267 | } // namespace tf_tracking
268 |
269 | #endif
270 |
--------------------------------------------------------------------------------
/app/src/main/jni/object_tracking/image_utils.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | #ifndef TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_IMAGE_UTILS_H_
17 | #define TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_IMAGE_UTILS_H_
18 |
19 | #include
20 |
21 | #include "geom.h"
22 | #include "image-inl.h"
23 | #include "image.h"
24 | #include "utils.h"
25 |
26 |
27 | namespace tf_tracking {
28 |
29 | inline void GetUV(const uint8_t* const input, Image* const u,
30 | Image* const v) {
31 | const uint8_t* pUV = input;
32 |
33 | for (int row = 0; row < u->GetHeight(); ++row) {
34 | uint8_t* u_curr = (*u)[row];
35 | uint8_t* v_curr = (*v)[row];
36 | for (int col = 0; col < u->GetWidth(); ++col) {
37 | #ifdef __APPLE__
38 | *u_curr++ = *pUV++;
39 | *v_curr++ = *pUV++;
40 | #else
41 | *v_curr++ = *pUV++;
42 | *u_curr++ = *pUV++;
43 | #endif
44 | }
45 | }
46 | }
47 |
48 | // Marks every point within a circle of a given radius on the given boolean
49 | // image true.
50 | template
51 | inline static void MarkImage(const int x, const int y, const int radius,
52 | Image* const img) {
53 | SCHECK(img->ValidPixel(x, y), "Marking invalid pixel in image! %d, %d", x, y);
54 |
55 | // Precomputed for efficiency.
56 | const int squared_radius = Square(radius);
57 |
58 | // Mark every row in the circle.
59 | for (int d_y = 0; d_y <= radius; ++d_y) {
60 | const int squared_y_dist = Square(d_y);
61 |
62 | const int min_y = MAX(y - d_y, 0);
63 | const int max_y = MIN(y + d_y, img->height_less_one_);
64 |
65 | // The max d_x of the circle must be strictly greater or equal to
66 | // radius - d_y for any positive d_y. Thus, starting from radius - d_y will
67 | // reduce the number of iterations required as compared to starting from
68 | // either 0 and counting up or radius and counting down.
69 | for (int d_x = radius - d_y; d_x <= radius; ++d_x) {
70 | // The first time this criteria is met, we know the width of the circle at
71 | // this row (without using sqrt).
72 | if (squared_y_dist + Square(d_x) >= squared_radius) {
73 | const int min_x = MAX(x - d_x, 0);
74 | const int max_x = MIN(x + d_x, img->width_less_one_);
75 |
76 | // Mark both above and below the center row.
77 | bool* const top_row_start = (*img)[min_y] + min_x;
78 | bool* const bottom_row_start = (*img)[max_y] + min_x;
79 |
80 | const int x_width = max_x - min_x + 1;
81 | memset(top_row_start, true, sizeof(*top_row_start) * x_width);
82 | memset(bottom_row_start, true, sizeof(*bottom_row_start) * x_width);
83 |
84 | // This row is marked, time to move on to the next row.
85 | break;
86 | }
87 | }
88 | }
89 | }
90 |
91 | #ifdef __ARM_NEON
92 | void CalculateGNeon(
93 | const float* const vals_x, const float* const vals_y,
94 | const int num_vals, float* const G);
95 | #endif
96 |
97 | // Puts the image gradient matrix about a pixel into the 2x2 float array G.
98 | // vals_x should be an array of the window x gradient values, whose indices
99 | // can be in any order but are parallel to the vals_y entries.
100 | // See http://robots.stanford.edu/cs223b04/algo_tracking.pdf for more details.
101 | inline void CalculateG(const float* const vals_x, const float* const vals_y,
102 | const int num_vals, float* const G) {
103 | #ifdef __ARM_NEON
104 | CalculateGNeon(vals_x, vals_y, num_vals, G);
105 | return;
106 | #endif
107 |
108 | // Non-accelerated version.
109 | for (int i = 0; i < num_vals; ++i) {
110 | G[0] += Square(vals_x[i]);
111 | G[1] += vals_x[i] * vals_y[i];
112 | G[3] += Square(vals_y[i]);
113 | }
114 |
115 | // The matrix is symmetric, so this is a given.
116 | G[2] = G[1];
117 | }
118 |
119 | inline void CalculateGInt16(const int16_t* const vals_x,
120 | const int16_t* const vals_y, const int num_vals,
121 | int* const G) {
122 | // Non-accelerated version.
123 | for (int i = 0; i < num_vals; ++i) {
124 | G[0] += Square(vals_x[i]);
125 | G[1] += vals_x[i] * vals_y[i];
126 | G[3] += Square(vals_y[i]);
127 | }
128 |
129 | // The matrix is symmetric, so this is a given.
130 | G[2] = G[1];
131 | }
132 |
133 |
134 | // Puts the image gradient matrix about a pixel into the 2x2 float array G.
135 | // Looks up interpolated pixels, then calls above method for implementation.
136 | inline void CalculateG(const int window_radius, const float center_x,
137 | const float center_y, const Image& I_x,
138 | const Image& I_y, float* const G) {
139 | SCHECK(I_x.ValidPixel(center_x, center_y), "Problem in calculateG!");
140 |
141 | // Hardcoded to allow for a max window radius of 5 (9 pixels x 9 pixels).
142 | static const int kMaxWindowRadius = 5;
143 | SCHECK(window_radius <= kMaxWindowRadius,
144 | "Window %d > %d!", window_radius, kMaxWindowRadius);
145 |
146 | // Diameter of window is 2 * radius + 1 for center pixel.
147 | static const int kWindowBufferSize =
148 | (kMaxWindowRadius * 2 + 1) * (kMaxWindowRadius * 2 + 1);
149 |
150 | // Preallocate buffers statically for efficiency.
151 | static int16_t vals_x[kWindowBufferSize];
152 | static int16_t vals_y[kWindowBufferSize];
153 |
154 | const int src_left_fixed = RealToFixed1616(center_x - window_radius);
155 | const int src_top_fixed = RealToFixed1616(center_y - window_radius);
156 |
157 | int16_t* vals_x_ptr = vals_x;
158 | int16_t* vals_y_ptr = vals_y;
159 |
160 | const int window_size = 2 * window_radius + 1;
161 | for (int y = 0; y < window_size; ++y) {
162 | const int fp_y = src_top_fixed + (y << 16);
163 |
164 | for (int x = 0; x < window_size; ++x) {
165 | const int fp_x = src_left_fixed + (x << 16);
166 |
167 | *vals_x_ptr++ = I_x.GetPixelInterpFixed1616(fp_x, fp_y);
168 | *vals_y_ptr++ = I_y.GetPixelInterpFixed1616(fp_x, fp_y);
169 | }
170 | }
171 |
172 | int32_t g_temp[] = {0, 0, 0, 0};
173 | CalculateGInt16(vals_x, vals_y, window_size * window_size, g_temp);
174 |
175 | for (int i = 0; i < 4; ++i) {
176 | G[i] = g_temp[i];
177 | }
178 | }
179 |
180 | inline float ImageCrossCorrelation(const Image& image1,
181 | const Image& image2,
182 | const int x_offset, const int y_offset) {
183 | SCHECK(image1.GetWidth() == image2.GetWidth() &&
184 | image1.GetHeight() == image2.GetHeight(),
185 | "Dimension mismatch! %dx%d vs %dx%d",
186 | image1.GetWidth(), image1.GetHeight(),
187 | image2.GetWidth(), image2.GetHeight());
188 |
189 | const int num_pixels = image1.GetWidth() * image1.GetHeight();
190 | const float* data1 = image1.data();
191 | const float* data2 = image2.data();
192 | return ComputeCrossCorrelation(data1, data2, num_pixels);
193 | }
194 |
195 | // Copies an arbitrary region of an image to another (floating point)
196 | // image, scaling as it goes using bilinear interpolation.
197 | inline void CopyArea(const Image& image,
198 | const BoundingBox& area_to_copy,
199 | Image* const patch_image) {
200 | VLOG(2) << "Copying from: " << area_to_copy << std::endl;
201 |
202 | const int patch_width = patch_image->GetWidth();
203 | const int patch_height = patch_image->GetHeight();
204 |
205 | const float x_dist_between_samples = patch_width > 0 ?
206 | area_to_copy.GetWidth() / (patch_width - 1) : 0;
207 |
208 | const float y_dist_between_samples = patch_height > 0 ?
209 | area_to_copy.GetHeight() / (patch_height - 1) : 0;
210 |
211 | for (int y_index = 0; y_index < patch_height; ++y_index) {
212 | const float sample_y =
213 | y_index * y_dist_between_samples + area_to_copy.top_;
214 |
215 | for (int x_index = 0; x_index < patch_width; ++x_index) {
216 | const float sample_x =
217 | x_index * x_dist_between_samples + area_to_copy.left_;
218 |
219 | if (image.ValidInterpPixel(sample_x, sample_y)) {
220 | // TODO(andrewharp): Do area averaging when downsampling.
221 | (*patch_image)[y_index][x_index] =
222 | image.GetPixelInterp(sample_x, sample_y);
223 | } else {
224 | (*patch_image)[y_index][x_index] = -1.0f;
225 | }
226 | }
227 | }
228 | }
229 |
230 |
231 | // Takes a floating point image and normalizes it in-place.
232 | //
233 | // First, negative values will be set to the mean of the non-negative pixels
234 | // in the image.
235 | //
236 | // Then, the resulting will be normalized such that it has mean value of 0.0 and
237 | // a standard deviation of 1.0.
238 | inline void NormalizeImage(Image* const image) {
239 | const float* const data_ptr = image->data();
240 |
241 | // Copy only the non-negative values to some temp memory.
242 | float running_sum = 0.0f;
243 | int num_data_gte_zero = 0;
244 | {
245 | float* const curr_data = (*image)[0];
246 | for (int i = 0; i < image->data_size_; ++i) {
247 | if (curr_data[i] >= 0.0f) {
248 | running_sum += curr_data[i];
249 | ++num_data_gte_zero;
250 | } else {
251 | curr_data[i] = -1.0f;
252 | }
253 | }
254 | }
255 |
256 | // If none of the pixels are valid, just set the entire thing to 0.0f.
257 | if (num_data_gte_zero == 0) {
258 | image->Clear(0.0f);
259 | return;
260 | }
261 |
262 | const float corrected_mean = running_sum / num_data_gte_zero;
263 |
264 | float* curr_data = (*image)[0];
265 | for (int i = 0; i < image->data_size_; ++i) {
266 | const float curr_val = *curr_data;
267 | *curr_data++ = curr_val < 0 ? 0 : curr_val - corrected_mean;
268 | }
269 |
270 | const float std_dev = ComputeStdDev(data_ptr, image->data_size_, 0.0f);
271 |
272 | if (std_dev > 0.0f) {
273 | curr_data = (*image)[0];
274 | for (int i = 0; i < image->data_size_; ++i) {
275 | *curr_data++ /= std_dev;
276 | }
277 |
278 | #ifdef SANITY_CHECKS
279 | LOGV("corrected_mean: %1.2f std_dev: %1.2f", corrected_mean, std_dev);
280 | const float correlation =
281 | ComputeCrossCorrelation(image->data(),
282 | image->data(),
283 | image->data_size_);
284 |
285 | if (std::abs(correlation - 1.0f) > EPSILON) {
286 | LOG(ERROR) << "Bad image!" << std::endl;
287 | LOG(ERROR) << *image << std::endl;
288 | }
289 |
290 | SCHECK(std::abs(correlation - 1.0f) < EPSILON,
291 | "Correlation wasn't 1.0f: %.10f", correlation);
292 | #endif
293 | }
294 | }
295 |
296 | } // namespace tf_tracking
297 |
298 | #endif // TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_IMAGE_UTILS_H_
299 |
--------------------------------------------------------------------------------
/app/src/main/jni/object_tracking/integral_image.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | #ifndef TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_INTEGRAL_IMAGE_H_
17 | #define TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_INTEGRAL_IMAGE_H_
18 |
19 | #include "geom.h"
20 | #include "image-inl.h"
21 | #include "image.h"
22 | #include "utils.h"
23 |
24 | namespace tf_tracking {
25 |
26 | typedef uint8_t Code;
27 |
28 | class IntegralImage : public Image {
29 | public:
30 | explicit IntegralImage(const Image& image_base)
31 | : Image(image_base.GetWidth(), image_base.GetHeight()) {
32 | Recompute(image_base);
33 | }
34 |
35 | IntegralImage(const int width, const int height)
36 | : Image(width, height) {}
37 |
38 | void Recompute(const Image& image_base) {
39 | SCHECK(image_base.GetWidth() == GetWidth() &&
40 | image_base.GetHeight() == GetHeight(), "Dimensions don't match!");
41 |
42 | // Sum along first row.
43 | {
44 | int x_sum = 0;
45 | for (int x = 0; x < image_base.GetWidth(); ++x) {
46 | x_sum += image_base[0][x];
47 | (*this)[0][x] = x_sum;
48 | }
49 | }
50 |
51 | // Sum everything else.
52 | for (int y = 1; y < image_base.GetHeight(); ++y) {
53 | uint32_t* curr_sum = (*this)[y];
54 |
55 | // Previously summed pointers.
56 | const uint32_t* up_one = (*this)[y - 1];
57 |
58 | // Current value pointer.
59 | const uint8_t* curr_delta = image_base[y];
60 |
61 | uint32_t row_till_now = 0;
62 |
63 | for (int x = 0; x < GetWidth(); ++x) {
64 | // Add the one above and the one to the left.
65 | row_till_now += *curr_delta;
66 | *curr_sum = *up_one + row_till_now;
67 |
68 | // Scoot everything along.
69 | ++curr_sum;
70 | ++up_one;
71 | ++curr_delta;
72 | }
73 | }
74 |
75 | SCHECK(VerifyData(image_base), "Images did not match!");
76 | }
77 |
78 | bool VerifyData(const Image& image_base) {
79 | for (int y = 0; y < GetHeight(); ++y) {
80 | for (int x = 0; x < GetWidth(); ++x) {
81 | uint32_t curr_val = (*this)[y][x];
82 |
83 | if (x > 0) {
84 | curr_val -= (*this)[y][x - 1];
85 | }
86 |
87 | if (y > 0) {
88 | curr_val -= (*this)[y - 1][x];
89 | }
90 |
91 | if (x > 0 && y > 0) {
92 | curr_val += (*this)[y - 1][x - 1];
93 | }
94 |
95 | if (curr_val != image_base[y][x]) {
96 | LOGE("Mismatch! %d vs %d", curr_val, image_base[y][x]);
97 | return false;
98 | }
99 |
100 | if (GetRegionSum(x, y, x, y) != curr_val) {
101 | LOGE("Mismatch!");
102 | }
103 | }
104 | }
105 |
106 | return true;
107 | }
108 |
109 | // Returns the sum of all pixels in the specified region.
110 | inline uint32_t GetRegionSum(const int x1, const int y1, const int x2,
111 | const int y2) const {
112 | SCHECK(x1 >= 0 && y1 >= 0 &&
113 | x2 >= x1 && y2 >= y1 && x2 < GetWidth() && y2 < GetHeight(),
114 | "indices out of bounds! %d-%d / %d, %d-%d / %d, ",
115 | x1, x2, GetWidth(), y1, y2, GetHeight());
116 |
117 | const uint32_t everything = (*this)[y2][x2];
118 |
119 | uint32_t sum = everything;
120 | if (x1 > 0 && y1 > 0) {
121 | // Most common case.
122 | const uint32_t left = (*this)[y2][x1 - 1];
123 | const uint32_t top = (*this)[y1 - 1][x2];
124 | const uint32_t top_left = (*this)[y1 - 1][x1 - 1];
125 |
126 | sum = everything - left - top + top_left;
127 | SCHECK(sum >= 0, "Both: %d - %d - %d + %d => %d! indices: %d %d %d %d",
128 | everything, left, top, top_left, sum, x1, y1, x2, y2);
129 | } else if (x1 > 0) {
130 | // Flush against top of image.
131 | // Subtract out the region to the left only.
132 | const uint32_t top = (*this)[y2][x1 - 1];
133 | sum = everything - top;
134 | SCHECK(sum >= 0, "Top: %d - %d => %d!", everything, top, sum);
135 | } else if (y1 > 0) {
136 | // Flush against left side of image.
137 | // Subtract out the region above only.
138 | const uint32_t left = (*this)[y1 - 1][x2];
139 | sum = everything - left;
140 | SCHECK(sum >= 0, "Left: %d - %d => %d!", everything, left, sum);
141 | }
142 |
143 | SCHECK(sum >= 0, "Negative sum!");
144 |
145 | return sum;
146 | }
147 |
148 | // Returns the 2bit code associated with this region, which represents
149 | // the overall gradient.
150 | inline Code GetCode(const BoundingBox& bounding_box) const {
151 | return GetCode(bounding_box.left_, bounding_box.top_,
152 | bounding_box.right_, bounding_box.bottom_);
153 | }
154 |
155 | inline Code GetCode(const int x1, const int y1,
156 | const int x2, const int y2) const {
157 | SCHECK(x1 < x2 && y1 < y2, "Bounds out of order!! TL:%d,%d BR:%d,%d",
158 | x1, y1, x2, y2);
159 |
160 | // Gradient computed vertically.
161 | const int box_height = (y2 - y1) / 2;
162 | const int top_sum = GetRegionSum(x1, y1, x2, y1 + box_height);
163 | const int bottom_sum = GetRegionSum(x1, y2 - box_height, x2, y2);
164 | const bool vertical_code = top_sum > bottom_sum;
165 |
166 | // Gradient computed horizontally.
167 | const int box_width = (x2 - x1) / 2;
168 | const int left_sum = GetRegionSum(x1, y1, x1 + box_width, y2);
169 | const int right_sum = GetRegionSum(x2 - box_width, y1, x2, y2);
170 | const bool horizontal_code = left_sum > right_sum;
171 |
172 | const Code final_code = (vertical_code << 1) | horizontal_code;
173 |
174 | SCHECK(InRange(final_code, static_cast(0), static_cast(3)),
175 | "Invalid code! %d", final_code);
176 |
177 | // Returns a value 0-3.
178 | return final_code;
179 | }
180 |
181 | private:
182 | TF_DISALLOW_COPY_AND_ASSIGN(IntegralImage);
183 | };
184 |
185 | } // namespace tf_tracking
186 |
187 | #endif // TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_INTEGRAL_IMAGE_H_
188 |
--------------------------------------------------------------------------------
/app/src/main/jni/object_tracking/jni_utils.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | #ifndef TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_JNI_UTILS_H_
17 | #define TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_JNI_UTILS_H_
18 |
19 | #include
20 |
21 | #include "utils.h"
22 |
23 | // The JniLongField class is used to access Java fields from native code. This
24 | // technique of hiding pointers to native objects in opaque Java fields is how
25 | // the Android hardware libraries work. This reduces the amount of static
26 | // native methods and makes it easier to manage the lifetime of native objects.
27 | class JniLongField {
28 | public:
29 | JniLongField(const char* field_name)
30 | : field_name_(field_name), field_ID_(0) {}
31 |
32 | int64_t get(JNIEnv* env, jobject thiz) {
33 | if (field_ID_ == 0) {
34 | jclass cls = env->GetObjectClass(thiz);
35 | CHECK_ALWAYS(cls != 0, "Unable to find class");
36 | field_ID_ = env->GetFieldID(cls, field_name_, "J");
37 | CHECK_ALWAYS(field_ID_ != 0,
38 | "Unable to find field %s. (Check proguard cfg)", field_name_);
39 | }
40 |
41 | return env->GetLongField(thiz, field_ID_);
42 | }
43 |
44 | void set(JNIEnv* env, jobject thiz, int64_t value) {
45 | if (field_ID_ == 0) {
46 | jclass cls = env->GetObjectClass(thiz);
47 | CHECK_ALWAYS(cls != 0, "Unable to find class");
48 | field_ID_ = env->GetFieldID(cls, field_name_, "J");
49 | CHECK_ALWAYS(field_ID_ != 0,
50 | "Unable to find field %s (Check proguard cfg)", field_name_);
51 | }
52 |
53 | env->SetLongField(thiz, field_ID_, value);
54 | }
55 |
56 | private:
57 | const char* const field_name_;
58 |
59 | // This is just a cache
60 | jfieldID field_ID_;
61 | };
62 |
63 | #endif
64 |
--------------------------------------------------------------------------------
/app/src/main/jni/object_tracking/keypoint.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | #ifndef TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_KEYPOINT_H_
17 | #define TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_KEYPOINT_H_
18 |
19 | #include "geom.h"
20 | #include "image-inl.h"
21 | #include "image.h"
22 | #include "utils.h"
23 |
24 | #include "config.h"
25 |
26 | namespace tf_tracking {
27 |
28 | // For keeping track of keypoints.
29 | struct Keypoint {
30 | Keypoint() : pos_(0.0f, 0.0f), score_(0.0f), type_(0) {}
31 | Keypoint(const float x, const float y)
32 | : pos_(x, y), score_(0.0f), type_(0) {}
33 |
34 | Point2f pos_;
35 | float score_;
36 | uint8_t type_;
37 | };
38 |
39 | inline std::ostream& operator<<(std::ostream& stream, const Keypoint keypoint) {
40 | return stream << "[" << keypoint.pos_ << ", "
41 | << keypoint.score_ << ", " << keypoint.type_ << "]";
42 | }
43 |
44 | } // namespace tf_tracking
45 |
46 | #endif // TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_KEYPOINT_H_
47 |
--------------------------------------------------------------------------------
/app/src/main/jni/object_tracking/keypoint_detector.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | #ifndef TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_KEYPOINT_DETECTOR_H_
17 | #define TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_KEYPOINT_DETECTOR_H_
18 |
19 | #include
20 | #include
21 |
22 | #include "image-inl.h"
23 | #include "image.h"
24 | #include "image_data.h"
25 | #include "optical_flow.h"
26 |
27 | namespace tf_tracking {
28 |
29 | struct Keypoint;
30 |
31 | class KeypointDetector {
32 | public:
33 | explicit KeypointDetector(const KeypointDetectorConfig* const config)
34 | : config_(config),
35 | keypoint_scratch_(new Image(config_->image_size)),
36 | interest_map_(new Image(config_->image_size)),
37 | fast_quadrant_(0) {
38 | interest_map_->Clear(false);
39 | }
40 |
41 | ~KeypointDetector() {}
42 |
43 | // Finds a new set of keypoints for the current frame, picked from the current
44 | // set of keypoints and also from a set discovered via a keypoint detector.
45 | // Special attention is applied to make sure that keypoints are distributed
46 | // within the supplied ROIs.
47 | void FindKeypoints(const ImageData& image_data,
48 | const std::vector& rois,
49 | const FramePair& prev_change,
50 | FramePair* const curr_change);
51 |
52 | private:
53 | // Compute the corneriness of a point in the image.
54 | float HarrisFilter(const Image& I_x, const Image& I_y,
55 | const float x, const float y) const;
56 |
57 | // Adds a grid of candidate keypoints to the given box, up to
58 | // max_num_keypoints or kNumToAddAsCandidates^2, whichever is lower.
59 | int AddExtraCandidatesForBoxes(
60 | const std::vector& boxes,
61 | const int max_num_keypoints,
62 | Keypoint* const keypoints) const;
63 |
64 | // Scan the frame for potential keypoints using the FAST keypoint detector.
65 | // Quadrant is an argument 0-3 which refers to the quadrant of the image in
66 | // which to detect keypoints.
67 | int FindFastKeypoints(const Image& frame, const int quadrant,
68 | const int downsample_factor,
69 | const int max_num_keypoints, Keypoint* const keypoints);
70 |
71 | int FindFastKeypoints(const ImageData& image_data,
72 | const int max_num_keypoints,
73 | Keypoint* const keypoints);
74 |
75 | // Score a bunch of candidate keypoints. Assigns the scores to the input
76 | // candidate_keypoints array entries.
77 | void ScoreKeypoints(const ImageData& image_data,
78 | const int num_candidates,
79 | Keypoint* const candidate_keypoints);
80 |
81 | void SortKeypoints(const int num_candidates,
82 | Keypoint* const candidate_keypoints) const;
83 |
84 | // Selects a set of keypoints falling within the supplied box such that the
85 | // most highly rated keypoints are picked first, and so that none of them are
86 | // too close together.
87 | int SelectKeypointsInBox(
88 | const BoundingBox& box,
89 | const Keypoint* const candidate_keypoints,
90 | const int num_candidates,
91 | const int max_keypoints,
92 | const int num_existing_keypoints,
93 | const Keypoint* const existing_keypoints,
94 | Keypoint* const final_keypoints) const;
95 |
96 | // Selects from the supplied sorted keypoint pool a set of keypoints that will
97 | // best cover the given set of boxes, such that each box is covered at a
98 | // resolution proportional to its size.
99 | void SelectKeypoints(
100 | const std::vector& boxes,
101 | const Keypoint* const candidate_keypoints,
102 | const int num_candidates,
103 | FramePair* const frame_change) const;
104 |
105 | // Copies and compacts the found keypoints in the second frame of prev_change
106 | // into the array at new_keypoints.
107 | static int CopyKeypoints(const FramePair& prev_change,
108 | Keypoint* const new_keypoints);
109 |
110 | const KeypointDetectorConfig* const config_;
111 |
112 | // Scratch memory for keypoint candidacy detection and non-max suppression.
113 | std::unique_ptr > keypoint_scratch_;
114 |
115 | // Regions of the image to pay special attention to.
116 | std::unique_ptr > interest_map_;
117 |
118 | // The current quadrant of the image to detect FAST keypoints in.
119 | // Keypoint detection is staggered for performance reasons. Every four frames
120 | // a full scan of the frame will have been performed.
121 | int fast_quadrant_;
122 |
123 | Keypoint tmp_keypoints_[kMaxTempKeypoints];
124 | };
125 |
126 | } // namespace tf_tracking
127 |
128 | #endif // TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_KEYPOINT_DETECTOR_H_
129 |
--------------------------------------------------------------------------------
/app/src/main/jni/object_tracking/logging.cc:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | #include "logging.h"
17 |
18 | #ifdef STANDALONE_DEMO_LIB
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 |
26 | LogMessage::LogMessage(const char* fname, int line, int severity)
27 | : fname_(fname), line_(line), severity_(severity) {}
28 |
29 | void LogMessage::GenerateLogMessage() {
30 | int android_log_level;
31 | switch (severity_) {
32 | case INFO:
33 | android_log_level = ANDROID_LOG_INFO;
34 | break;
35 | case WARNING:
36 | android_log_level = ANDROID_LOG_WARN;
37 | break;
38 | case ERROR:
39 | android_log_level = ANDROID_LOG_ERROR;
40 | break;
41 | case FATAL:
42 | android_log_level = ANDROID_LOG_FATAL;
43 | break;
44 | default:
45 | if (severity_ < INFO) {
46 | android_log_level = ANDROID_LOG_VERBOSE;
47 | } else {
48 | android_log_level = ANDROID_LOG_ERROR;
49 | }
50 | break;
51 | }
52 |
53 | std::stringstream ss;
54 | const char* const partial_name = strrchr(fname_, '/');
55 | ss << (partial_name != nullptr ? partial_name + 1 : fname_) << ":" << line_
56 | << " " << str();
57 | __android_log_write(android_log_level, "native", ss.str().c_str());
58 |
59 | // Also log to stderr (for standalone Android apps).
60 | std::cerr << "native : " << ss.str() << std::endl;
61 |
62 | // Android logging at level FATAL does not terminate execution, so abort()
63 | // is still required to stop the program.
64 | if (severity_ == FATAL) {
65 | abort();
66 | }
67 | }
68 |
69 | namespace {
70 |
71 | // Parse log level (int64) from environment variable (char*)
72 | int64_t LogLevelStrToInt(const char* tf_env_var_val) {
73 | if (tf_env_var_val == nullptr) {
74 | return 0;
75 | }
76 |
77 | // Ideally we would use env_var / safe_strto64, but it is
78 | // hard to use here without pulling in a lot of dependencies,
79 | // so we use std:istringstream instead
80 | std::string min_log_level(tf_env_var_val);
81 | std::istringstream ss(min_log_level);
82 | int64_t level;
83 | if (!(ss >> level)) {
84 | // Invalid vlog level setting, set level to default (0)
85 | level = 0;
86 | }
87 |
88 | return level;
89 | }
90 |
91 | int64_t MinLogLevelFromEnv() {
92 | const char* tf_env_var_val = getenv("TF_CPP_MIN_LOG_LEVEL");
93 | return LogLevelStrToInt(tf_env_var_val);
94 | }
95 |
96 | int64_t MinVLogLevelFromEnv() {
97 | const char* tf_env_var_val = getenv("TF_CPP_MIN_VLOG_LEVEL");
98 | return LogLevelStrToInt(tf_env_var_val);
99 | }
100 |
101 | } // namespace
102 |
103 | LogMessage::~LogMessage() {
104 | // Read the min log level once during the first call to logging.
105 | static int64_t min_log_level = MinLogLevelFromEnv();
106 | if (TF_PREDICT_TRUE(severity_ >= min_log_level)) GenerateLogMessage();
107 | }
108 |
109 | int64_t LogMessage::MinVLogLevel() {
110 | static const int64_t min_vlog_level = MinVLogLevelFromEnv();
111 | return min_vlog_level;
112 | }
113 |
114 | LogMessageFatal::LogMessageFatal(const char* file, int line)
115 | : LogMessage(file, line, ANDROID_LOG_FATAL) {}
116 | LogMessageFatal::~LogMessageFatal() {
117 | // abort() ensures we don't return (we promised we would not via
118 | // ATTRIBUTE_NORETURN).
119 | GenerateLogMessage();
120 | abort();
121 | }
122 |
123 | void LogString(const char* fname, int line, int severity,
124 | const std::string& message) {
125 | LogMessage(fname, line, severity) << message;
126 | }
127 |
128 | void LogPrintF(const int severity, const char* format, ...) {
129 | char message[1024];
130 | va_list argptr;
131 | va_start(argptr, format);
132 | vsnprintf(message, 1024, format, argptr);
133 | va_end(argptr);
134 | __android_log_write(severity, "native", message);
135 |
136 | // Also log to stderr (for standalone Android apps).
137 | std::cerr << "native : " << message << std::endl;
138 | }
139 |
140 | #endif
141 |
--------------------------------------------------------------------------------
/app/src/main/jni/object_tracking/logging.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | #ifndef TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_LOG_STREAMING_H_
17 | #define TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_LOG_STREAMING_H_
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 |
25 | // Allow this library to be built without depending on TensorFlow by
26 | // defining STANDALONE_DEMO_LIB. Otherwise TensorFlow headers will be
27 | // used.
28 | #ifdef STANDALONE_DEMO_LIB
29 |
30 | // A macro to disallow the copy constructor and operator= functions
31 | // This is usually placed in the private: declarations for a class.
32 | #define TF_DISALLOW_COPY_AND_ASSIGN(TypeName) \
33 | TypeName(const TypeName&) = delete; \
34 | void operator=(const TypeName&) = delete
35 |
36 | #if defined(COMPILER_GCC3)
37 | #define TF_PREDICT_FALSE(x) (__builtin_expect(x, 0))
38 | #define TF_PREDICT_TRUE(x) (__builtin_expect(!!(x), 1))
39 | #else
40 | #define TF_PREDICT_FALSE(x) (x)
41 | #define TF_PREDICT_TRUE(x) (x)
42 | #endif
43 |
44 | // Log levels equivalent to those defined by
45 | // third_party/tensorflow/core/platform/logging.h
46 | const int INFO = 0; // base_logging::INFO;
47 | const int WARNING = 1; // base_logging::WARNING;
48 | const int ERROR = 2; // base_logging::ERROR;
49 | const int FATAL = 3; // base_logging::FATAL;
50 | const int NUM_SEVERITIES = 4; // base_logging::NUM_SEVERITIES;
51 |
52 | class LogMessage : public std::basic_ostringstream {
53 | public:
54 | LogMessage(const char* fname, int line, int severity);
55 | ~LogMessage();
56 |
57 | // Returns the minimum log level for VLOG statements.
58 | // E.g., if MinVLogLevel() is 2, then VLOG(2) statements will produce output,
59 | // but VLOG(3) will not. Defaults to 0.
60 | static int64_t MinVLogLevel();
61 |
62 | protected:
63 | void GenerateLogMessage();
64 |
65 | private:
66 | const char* fname_;
67 | int line_;
68 | int severity_;
69 | };
70 |
71 | // LogMessageFatal ensures the process will exit in failure after
72 | // logging this message.
73 | class LogMessageFatal : public LogMessage {
74 | public:
75 | LogMessageFatal(const char* file, int line);
76 | ~LogMessageFatal();
77 | };
78 |
79 | #define _TF_LOG_INFO \
80 | ::tensorflow::internal::LogMessage(__FILE__, __LINE__, tensorflow::INFO)
81 | #define _TF_LOG_WARNING \
82 | ::tensorflow::internal::LogMessage(__FILE__, __LINE__, tensorflow::WARNING)
83 | #define _TF_LOG_ERROR \
84 | ::tensorflow::internal::LogMessage(__FILE__, __LINE__, tensorflow::ERROR)
85 | #define _TF_LOG_FATAL \
86 | ::tensorflow::internal::LogMessageFatal(__FILE__, __LINE__)
87 |
88 | #define _TF_LOG_QFATAL _TF_LOG_FATAL
89 |
90 | #define LOG(severity) _TF_LOG_##severity
91 |
92 | #define VLOG_IS_ON(lvl) ((lvl) <= LogMessage::MinVLogLevel())
93 |
94 | #define VLOG(lvl) \
95 | if (TF_PREDICT_FALSE(VLOG_IS_ON(lvl))) \
96 | LogMessage(__FILE__, __LINE__, ANDROID_LOG_INFO)
97 |
98 | void LogPrintF(const int severity, const char* format, ...);
99 |
100 | // Support for printf style logging.
101 | #define LOGV(...)
102 | #define LOGD(...)
103 | #define LOGI(...) LogPrintF(ANDROID_LOG_INFO, __VA_ARGS__);
104 | #define LOGW(...) LogPrintF(ANDROID_LOG_INFO, __VA_ARGS__);
105 | #define LOGE(...) LogPrintF(ANDROID_LOG_ERROR, __VA_ARGS__);
106 |
107 | #else
108 |
109 | #include "tensorflow/core/lib/strings/stringprintf.h"
110 | #include "tensorflow/core/platform/logging.h"
111 |
112 | // Support for printf style logging.
113 | #define LOGV(...)
114 | #define LOGD(...)
115 | #define LOGI(...) LOG(INFO) << tensorflow::strings::Printf(__VA_ARGS__);
116 | #define LOGW(...) LOG(INFO) << tensorflow::strings::Printf(__VA_ARGS__);
117 | #define LOGE(...) LOG(INFO) << tensorflow::strings::Printf(__VA_ARGS__);
118 |
119 | #endif
120 |
121 | #endif // TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_LOG_STREAMING_H_
122 |
--------------------------------------------------------------------------------
/app/src/main/jni/object_tracking/object_detector.cc:
--------------------------------------------------------------------------------
1 | /* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | // NOTE: no native object detectors are currently provided or used by the code
17 | // in this directory. This class remains mainly for historical reasons.
18 | // Detection in the TF demo is done through TensorFlowMultiBoxDetector.java.
19 |
20 | #include "object_detector.h"
21 |
22 | namespace tf_tracking {
23 |
24 | // This is here so that the vtable gets created properly.
25 | ObjectDetectorBase::~ObjectDetectorBase() {}
26 |
27 | } // namespace tf_tracking
28 |
--------------------------------------------------------------------------------
/app/src/main/jni/object_tracking/object_detector.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | // NOTE: no native object detectors are currently provided or used by the code
17 | // in this directory. This class remains mainly for historical reasons.
18 | // Detection in the TF demo is done through TensorFlowMultiBoxDetector.java.
19 |
20 | // Defines the ObjectDetector class that is the main interface for detecting
21 | // ObjectModelBases in frames.
22 |
23 | #ifndef TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_OBJECT_DETECTOR_H_
24 | #define TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_OBJECT_DETECTOR_H_
25 |
26 | #include
27 | #include