diff --git a/android-tflite/app/build.gradle b/android-tflite/app/build.gradle
new file mode 100755
index 0000000..603f0d4
--- /dev/null
+++ b/android-tflite/app/build.gradle
@@ -0,0 +1,59 @@
+apply plugin: 'com.android.application'
+
+project.ext.ASSET_DIR = projectDir.toString() + '/src/main/assets'
+
+assert file(project.ext.ASSET_DIR + "/graph.lite").exists()
+assert file(project.ext.ASSET_DIR + "/labels.txt").exists()
+
+android {
+ compileSdkVersion 26
+ defaultConfig {
+ applicationId "android.example.com.tflitecamerademo"
+ minSdkVersion 21
+ targetSdkVersion 26
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ lintOptions {
+ abortOnError false
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+ aaptOptions {
+ noCompress "tflite"
+ noCompress "lite"
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+repositories {
+ maven {
+ url 'https://google.bintray.com/tensorflow'
+ }
+ google()
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ })
+ implementation 'com.android.support:appcompat-v7:25.2.0'
+ implementation 'com.android.support.constraint:constraint-layout:1.1.0'
+ implementation 'com.android.support:design:25.2.0'
+ implementation 'com.android.support:support-annotations:25.3.1'
+ implementation 'com.android.support:support-v13:25.2.0'
+
+ implementation 'org.tensorflow:tensorflow-lite:+'
+
+ testImplementation 'junit:junit:4.12'
+}
diff --git a/android-tflite/app/src/main/AndroidManifest.xml b/android-tflite/app/src/main/AndroidManifest.xml
new file mode 100755
index 0000000..b127731
--- /dev/null
+++ b/android-tflite/app/src/main/AndroidManifest.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android-tflite/app/src/main/assets/BUILD b/android-tflite/app/src/main/assets/BUILD
new file mode 100755
index 0000000..dd0cd6c
--- /dev/null
+++ b/android-tflite/app/src/main/assets/BUILD
@@ -0,0 +1,24 @@
+package(default_visibility = ["//visibility:private"])
+
+licenses(["notice"]) # Apache 2.0
+
+exports_files(
+ glob(
+ ["**/*"],
+ exclude = [
+ "BUILD",
+ ],
+ ),
+)
+
+filegroup(
+ name = "all_files",
+ srcs = glob(
+ ["**/*"],
+ exclude = [
+ "**/METADATA",
+ "**/OWNERS",
+ ],
+ ),
+ visibility = ["//tensorflow:__subpackages__"],
+)
diff --git a/android-tflite/app/src/main/assets/graph.lite b/android-tflite/app/src/main/assets/graph.lite
new file mode 100755
index 0000000..bd1c83a
Binary files /dev/null and b/android-tflite/app/src/main/assets/graph.lite differ
diff --git a/android-tflite/app/src/main/assets/labels.txt b/android-tflite/app/src/main/assets/labels.txt
new file mode 100755
index 0000000..fe81123
--- /dev/null
+++ b/android-tflite/app/src/main/assets/labels.txt
@@ -0,0 +1,1001 @@
+background
+tench
+goldfish
+great white shark
+tiger shark
+hammerhead
+electric ray
+stingray
+cock
+hen
+ostrich
+brambling
+goldfinch
+house finch
+junco
+indigo bunting
+robin
+bulbul
+jay
+magpie
+chickadee
+water ouzel
+kite
+bald eagle
+vulture
+great grey owl
+European fire salamander
+common newt
+eft
+spotted salamander
+axolotl
+bullfrog
+tree frog
+tailed frog
+loggerhead
+leatherback turtle
+mud turtle
+terrapin
+box turtle
+banded gecko
+common iguana
+American chameleon
+whiptail
+agama
+frilled lizard
+alligator lizard
+Gila monster
+green lizard
+African chameleon
+Komodo dragon
+African crocodile
+American alligator
+triceratops
+thunder snake
+ringneck snake
+hognose snake
+green snake
+king snake
+garter snake
+water snake
+vine snake
+night snake
+boa constrictor
+rock python
+Indian cobra
+green mamba
+sea snake
+horned viper
+diamondback
+sidewinder
+trilobite
+harvestman
+scorpion
+black and gold garden spider
+barn spider
+garden spider
+black widow
+tarantula
+wolf spider
+tick
+centipede
+black grouse
+ptarmigan
+ruffed grouse
+prairie chicken
+peacock
+quail
+partridge
+African grey
+macaw
+sulphur-crested cockatoo
+lorikeet
+coucal
+bee eater
+hornbill
+hummingbird
+jacamar
+toucan
+drake
+red-breasted merganser
+goose
+black swan
+tusker
+echidna
+platypus
+wallaby
+koala
+wombat
+jellyfish
+sea anemone
+brain coral
+flatworm
+nematode
+conch
+snail
+slug
+sea slug
+chiton
+chambered nautilus
+Dungeness crab
+rock crab
+fiddler crab
+king crab
+American lobster
+spiny lobster
+crayfish
+hermit crab
+isopod
+white stork
+black stork
+spoonbill
+flamingo
+little blue heron
+American egret
+bittern
+crane
+limpkin
+European gallinule
+American coot
+bustard
+ruddy turnstone
+red-backed sandpiper
+redshank
+dowitcher
+oystercatcher
+pelican
+king penguin
+albatross
+grey whale
+killer whale
+dugong
+sea lion
+Chihuahua
+Japanese spaniel
+Maltese dog
+Pekinese
+Shih-Tzu
+Blenheim spaniel
+papillon
+toy terrier
+Rhodesian ridgeback
+Afghan hound
+basset
+beagle
+bloodhound
+bluetick
+black-and-tan coonhound
+Walker hound
+English foxhound
+redbone
+borzoi
+Irish wolfhound
+Italian greyhound
+whippet
+Ibizan hound
+Norwegian elkhound
+otterhound
+Saluki
+Scottish deerhound
+Weimaraner
+Staffordshire bullterrier
+American Staffordshire terrier
+Bedlington terrier
+Border terrier
+Kerry blue terrier
+Irish terrier
+Norfolk terrier
+Norwich terrier
+Yorkshire terrier
+wire-haired fox terrier
+Lakeland terrier
+Sealyham terrier
+Airedale
+cairn
+Australian terrier
+Dandie Dinmont
+Boston bull
+miniature schnauzer
+giant schnauzer
+standard schnauzer
+Scotch terrier
+Tibetan terrier
+silky terrier
+soft-coated wheaten terrier
+West Highland white terrier
+Lhasa
+flat-coated retriever
+curly-coated retriever
+golden retriever
+Labrador retriever
+Chesapeake Bay retriever
+German short-haired pointer
+vizsla
+English setter
+Irish setter
+Gordon setter
+Brittany spaniel
+clumber
+English springer
+Welsh springer spaniel
+cocker spaniel
+Sussex spaniel
+Irish water spaniel
+kuvasz
+schipperke
+groenendael
+malinois
+briard
+kelpie
+komondor
+Old English sheepdog
+Shetland sheepdog
+collie
+Border collie
+Bouvier des Flandres
+Rottweiler
+German shepherd
+Doberman
+miniature pinscher
+Greater Swiss Mountain dog
+Bernese mountain dog
+Appenzeller
+EntleBucher
+boxer
+bull mastiff
+Tibetan mastiff
+French bulldog
+Great Dane
+Saint Bernard
+Eskimo dog
+malamute
+Siberian husky
+dalmatian
+affenpinscher
+basenji
+pug
+Leonberg
+Newfoundland
+Great Pyrenees
+Samoyed
+Pomeranian
+chow
+keeshond
+Brabancon griffon
+Pembroke
+Cardigan
+toy poodle
+miniature poodle
+standard poodle
+Mexican hairless
+timber wolf
+white wolf
+red wolf
+coyote
+dingo
+dhole
+African hunting dog
+hyena
+red fox
+kit fox
+Arctic fox
+grey fox
+tabby
+tiger cat
+Persian cat
+Siamese cat
+Egyptian cat
+cougar
+lynx
+leopard
+snow leopard
+jaguar
+lion
+tiger
+cheetah
+brown bear
+American black bear
+ice bear
+sloth bear
+mongoose
+meerkat
+tiger beetle
+ladybug
+ground beetle
+long-horned beetle
+leaf beetle
+dung beetle
+rhinoceros beetle
+weevil
+fly
+bee
+ant
+grasshopper
+cricket
+walking stick
+cockroach
+mantis
+cicada
+leafhopper
+lacewing
+dragonfly
+damselfly
+admiral
+ringlet
+monarch
+cabbage butterfly
+sulphur butterfly
+lycaenid
+starfish
+sea urchin
+sea cucumber
+wood rabbit
+hare
+Angora
+hamster
+porcupine
+fox squirrel
+marmot
+beaver
+guinea pig
+sorrel
+zebra
+hog
+wild boar
+warthog
+hippopotamus
+ox
+water buffalo
+bison
+ram
+bighorn
+ibex
+hartebeest
+impala
+gazelle
+Arabian camel
+llama
+weasel
+mink
+polecat
+black-footed ferret
+otter
+skunk
+badger
+armadillo
+three-toed sloth
+orangutan
+gorilla
+chimpanzee
+gibbon
+siamang
+guenon
+patas
+baboon
+macaque
+langur
+colobus
+proboscis monkey
+marmoset
+capuchin
+howler monkey
+titi
+spider monkey
+squirrel monkey
+Madagascar cat
+indri
+Indian elephant
+African elephant
+lesser panda
+giant panda
+barracouta
+eel
+coho
+rock beauty
+anemone fish
+sturgeon
+gar
+lionfish
+puffer
+abacus
+abaya
+academic gown
+accordion
+acoustic guitar
+aircraft carrier
+airliner
+airship
+altar
+ambulance
+amphibian
+analog clock
+apiary
+apron
+ashcan
+assault rifle
+backpack
+bakery
+balance beam
+balloon
+ballpoint
+Band Aid
+banjo
+bannister
+barbell
+barber chair
+barbershop
+barn
+barometer
+barrel
+barrow
+baseball
+basketball
+bassinet
+bassoon
+bathing cap
+bath towel
+bathtub
+beach wagon
+beacon
+beaker
+bearskin
+beer bottle
+beer glass
+bell cote
+bib
+bicycle-built-for-two
+bikini
+binder
+binoculars
+birdhouse
+boathouse
+bobsled
+bolo tie
+bonnet
+bookcase
+bookshop
+bottlecap
+bow
+bow tie
+brass
+brassiere
+breakwater
+breastplate
+broom
+bucket
+buckle
+bulletproof vest
+bullet train
+butcher shop
+cab
+caldron
+candle
+cannon
+canoe
+can opener
+cardigan
+car mirror
+carousel
+carpenter's kit
+carton
+car wheel
+cash machine
+cassette
+cassette player
+castle
+catamaran
+CD player
+cello
+cellular telephone
+chain
+chainlink fence
+chain mail
+chain saw
+chest
+chiffonier
+chime
+china cabinet
+Christmas stocking
+church
+cinema
+cleaver
+cliff dwelling
+cloak
+clog
+cocktail shaker
+coffee mug
+coffeepot
+coil
+combination lock
+computer keyboard
+confectionery
+container ship
+convertible
+corkscrew
+cornet
+cowboy boot
+cowboy hat
+cradle
+crane
+crash helmet
+crate
+crib
+Crock Pot
+croquet ball
+crutch
+cuirass
+dam
+desk
+desktop computer
+dial telephone
+diaper
+digital clock
+digital watch
+dining table
+dishrag
+dishwasher
+disk brake
+dock
+dogsled
+dome
+doormat
+drilling platform
+drum
+drumstick
+dumbbell
+Dutch oven
+electric fan
+electric guitar
+electric locomotive
+entertainment center
+envelope
+espresso maker
+face powder
+feather boa
+file
+fireboat
+fire engine
+fire screen
+flagpole
+flute
+folding chair
+football helmet
+forklift
+fountain
+fountain pen
+four-poster
+freight car
+French horn
+frying pan
+fur coat
+garbage truck
+gasmask
+gas pump
+goblet
+go-kart
+golf ball
+golfcart
+gondola
+gong
+gown
+grand piano
+greenhouse
+grille
+grocery store
+guillotine
+hair slide
+hair spray
+half track
+hammer
+hamper
+hand blower
+hand-held computer
+handkerchief
+hard disc
+harmonica
+harp
+harvester
+hatchet
+holster
+home theater
+honeycomb
+hook
+hoopskirt
+horizontal bar
+horse cart
+hourglass
+iPod
+iron
+jack-o'-lantern
+jean
+jeep
+jersey
+jigsaw puzzle
+jinrikisha
+joystick
+kimono
+knee pad
+knot
+lab coat
+ladle
+lampshade
+laptop
+lawn mower
+lens cap
+letter opener
+library
+lifeboat
+lighter
+limousine
+liner
+lipstick
+Loafer
+lotion
+loudspeaker
+loupe
+lumbermill
+magnetic compass
+mailbag
+mailbox
+maillot
+maillot
+manhole cover
+maraca
+marimba
+mask
+matchstick
+maypole
+maze
+measuring cup
+medicine chest
+megalith
+microphone
+microwave
+military uniform
+milk can
+minibus
+miniskirt
+minivan
+missile
+mitten
+mixing bowl
+mobile home
+Model T
+modem
+monastery
+monitor
+moped
+mortar
+mortarboard
+mosque
+mosquito net
+motor scooter
+mountain bike
+mountain tent
+mouse
+mousetrap
+moving van
+muzzle
+nail
+neck brace
+necklace
+nipple
+notebook
+obelisk
+oboe
+ocarina
+odometer
+oil filter
+organ
+oscilloscope
+overskirt
+oxcart
+oxygen mask
+packet
+paddle
+paddlewheel
+padlock
+paintbrush
+pajama
+palace
+panpipe
+paper towel
+parachute
+parallel bars
+park bench
+parking meter
+passenger car
+patio
+pay-phone
+pedestal
+pencil box
+pencil sharpener
+perfume
+Petri dish
+photocopier
+pick
+pickelhaube
+picket fence
+pickup
+pier
+piggy bank
+pill bottle
+pillow
+ping-pong ball
+pinwheel
+pirate
+pitcher
+plane
+planetarium
+plastic bag
+plate rack
+plow
+plunger
+Polaroid camera
+pole
+police van
+poncho
+pool table
+pop bottle
+pot
+potter's wheel
+power drill
+prayer rug
+printer
+prison
+projectile
+projector
+puck
+punching bag
+purse
+quill
+quilt
+racer
+racket
+radiator
+radio
+radio telescope
+rain barrel
+recreational vehicle
+reel
+reflex camera
+refrigerator
+remote control
+restaurant
+revolver
+rifle
+rocking chair
+rotisserie
+rubber eraser
+rugby ball
+rule
+running shoe
+safe
+safety pin
+saltshaker
+sandal
+sarong
+sax
+scabbard
+scale
+school bus
+schooner
+scoreboard
+screen
+screw
+screwdriver
+seat belt
+sewing machine
+shield
+shoe shop
+shoji
+shopping basket
+shopping cart
+shovel
+shower cap
+shower curtain
+ski
+ski mask
+sleeping bag
+slide rule
+sliding door
+slot
+snorkel
+snowmobile
+snowplow
+soap dispenser
+soccer ball
+sock
+solar dish
+sombrero
+soup bowl
+space bar
+space heater
+space shuttle
+spatula
+speedboat
+spider web
+spindle
+sports car
+spotlight
+stage
+steam locomotive
+steel arch bridge
+steel drum
+stethoscope
+stole
+stone wall
+stopwatch
+stove
+strainer
+streetcar
+stretcher
+studio couch
+stupa
+submarine
+suit
+sundial
+sunglass
+sunglasses
+sunscreen
+suspension bridge
+swab
+sweatshirt
+swimming trunks
+swing
+switch
+syringe
+table lamp
+tank
+tape player
+teapot
+teddy
+television
+tennis ball
+thatch
+theater curtain
+thimble
+thresher
+throne
+tile roof
+toaster
+tobacco shop
+toilet seat
+torch
+totem pole
+tow truck
+toyshop
+tractor
+trailer truck
+tray
+trench coat
+tricycle
+trimaran
+tripod
+triumphal arch
+trolleybus
+trombone
+tub
+turnstile
+typewriter keyboard
+umbrella
+unicycle
+upright
+vacuum
+vase
+vault
+velvet
+vending machine
+vestment
+viaduct
+violin
+volleyball
+waffle iron
+wall clock
+wallet
+wardrobe
+warplane
+washbasin
+washer
+water bottle
+water jug
+water tower
+whiskey jug
+whistle
+wig
+window screen
+window shade
+Windsor tie
+wine bottle
+wing
+wok
+wooden spoon
+wool
+worm fence
+wreck
+yawl
+yurt
+web site
+comic book
+crossword puzzle
+street sign
+traffic light
+book jacket
+menu
+plate
+guacamole
+consomme
+hot pot
+trifle
+ice cream
+ice lolly
+French loaf
+bagel
+pretzel
+cheeseburger
+hotdog
+mashed potato
+head cabbage
+broccoli
+cauliflower
+zucchini
+spaghetti squash
+acorn squash
+butternut squash
+cucumber
+artichoke
+bell pepper
+cardoon
+mushroom
+Granny Smith
+strawberry
+orange
+lemon
+fig
+pineapple
+banana
+jackfruit
+custard apple
+pomegranate
+hay
+carbonara
+chocolate sauce
+dough
+meat loaf
+pizza
+potpie
+burrito
+red wine
+espresso
+cup
+eggnog
+alp
+bubble
+cliff
+coral reef
+geyser
+lakeside
+promontory
+sandbar
+seashore
+valley
+volcano
+ballplayer
+groom
+scuba diver
+rapeseed
+daisy
+yellow lady's slipper
+corn
+acorn
+hip
+buckeye
+coral fungus
+agaric
+gyromitra
+stinkhorn
+earthstar
+hen-of-the-woods
+bolete
+ear
+toilet tissue
diff --git a/android-tflite/app/src/main/java/com/example/android/tflitecamerademo/AutoFitTextureView.java b/android-tflite/app/src/main/java/com/example/android/tflitecamerademo/AutoFitTextureView.java
new file mode 100755
index 0000000..f204590
--- /dev/null
+++ b/android-tflite/app/src/main/java/com/example/android/tflitecamerademo/AutoFitTextureView.java
@@ -0,0 +1,72 @@
+/* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+package com.example.android.tflitecamerademo;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.TextureView;
+
+/** A {@link TextureView} that can be adjusted to a specified aspect ratio. */
+public class AutoFitTextureView extends TextureView {
+
+ private int mRatioWidth = 0;
+ private int mRatioHeight = 0;
+
+ public AutoFitTextureView(Context context) {
+ this(context, null);
+ }
+
+ public AutoFitTextureView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ /**
+ * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
+ * calculated from the parameters. Note that the actual sizes of parameters don't matter, that is,
+ * calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
+ *
+ * @param width Relative horizontal size
+ * @param height Relative vertical size
+ */
+ public void setAspectRatio(int width, int height) {
+ if (width < 0 || height < 0) {
+ throw new IllegalArgumentException("Size cannot be negative.");
+ }
+ mRatioWidth = width;
+ mRatioHeight = height;
+ requestLayout();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ if (0 == mRatioWidth || 0 == mRatioHeight) {
+ setMeasuredDimension(width, height);
+ } else {
+ if (width < height * mRatioWidth / mRatioHeight) {
+ setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
+ } else {
+ setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
+ }
+ }
+ }
+}
diff --git a/android-tflite/app/src/main/java/com/example/android/tflitecamerademo/Camera2BasicFragment.java b/android-tflite/app/src/main/java/com/example/android/tflitecamerademo/Camera2BasicFragment.java
new file mode 100755
index 0000000..74737a8
--- /dev/null
+++ b/android-tflite/app/src/main/java/com/example/android/tflitecamerademo/Camera2BasicFragment.java
@@ -0,0 +1,708 @@
+/* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+package com.example.android.tflitecamerademo;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.ImageFormat;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.ImageReader;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.support.annotation.NonNull;
+import android.support.v13.app.FragmentCompat;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
+import android.util.Size;
+import android.view.LayoutInflater;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import android.widget.Toast;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+/** Basic fragments for the Camera. */
+public class Camera2BasicFragment extends Fragment
+ implements FragmentCompat.OnRequestPermissionsResultCallback {
+
+ /** Tag for the {@link Log}. */
+ private static final String TAG = "TfLiteCameraDemo";
+
+ private static final String FRAGMENT_DIALOG = "dialog";
+
+ private static final String HANDLE_THREAD_NAME = "CameraBackground";
+
+ private static final int PERMISSIONS_REQUEST_CODE = 1;
+
+ private final Object lock = new Object();
+ private boolean runClassifier = false;
+ private boolean checkedPermissions = false;
+ private TextView textView;
+ private ImageClassifier classifier;
+
+ /** Max preview width that is guaranteed by Camera2 API */
+ private static final int MAX_PREVIEW_WIDTH = 1920;
+
+ /** Max preview height that is guaranteed by Camera2 API */
+ private static final int MAX_PREVIEW_HEIGHT = 1080;
+
+ /**
+ * {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a {@link
+ * TextureView}.
+ */
+ private final TextureView.SurfaceTextureListener surfaceTextureListener =
+ new TextureView.SurfaceTextureListener() {
+
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
+ openCamera(width, height);
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
+ configureTransform(width, height);
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
+ return true;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture texture) {}
+ };
+
+ /** ID of the current {@link CameraDevice}. */
+ private String cameraId;
+
+ /** An {@link AutoFitTextureView} for camera preview. */
+ private AutoFitTextureView textureView;
+
+ /** A {@link CameraCaptureSession } for camera preview. */
+ private CameraCaptureSession captureSession;
+
+ /** A reference to the opened {@link CameraDevice}. */
+ private CameraDevice cameraDevice;
+
+ /** The {@link android.util.Size} of camera preview. */
+ private Size previewSize;
+
+ /** {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state. */
+ private final CameraDevice.StateCallback stateCallback =
+ new CameraDevice.StateCallback() {
+
+ @Override
+ public void onOpened(@NonNull CameraDevice currentCameraDevice) {
+ // This method is called when the camera is opened. We start camera preview here.
+ cameraOpenCloseLock.release();
+ cameraDevice = currentCameraDevice;
+ createCameraPreviewSession();
+ }
+
+ @Override
+ public void onDisconnected(@NonNull CameraDevice currentCameraDevice) {
+ cameraOpenCloseLock.release();
+ currentCameraDevice.close();
+ cameraDevice = null;
+ }
+
+ @Override
+ public void onError(@NonNull CameraDevice currentCameraDevice, int error) {
+ cameraOpenCloseLock.release();
+ currentCameraDevice.close();
+ cameraDevice = null;
+ Activity activity = getActivity();
+ if (null != activity) {
+ activity.finish();
+ }
+ }
+ };
+
+ /** An additional thread for running tasks that shouldn't block the UI. */
+ private HandlerThread backgroundThread;
+
+ /** A {@link Handler} for running tasks in the background. */
+ private Handler backgroundHandler;
+
+ /** An {@link ImageReader} that handles image capture. */
+ private ImageReader imageReader;
+
+ /** {@link CaptureRequest.Builder} for the camera preview */
+ private CaptureRequest.Builder previewRequestBuilder;
+
+ /** {@link CaptureRequest} generated by {@link #previewRequestBuilder} */
+ private CaptureRequest previewRequest;
+
+ /** A {@link Semaphore} to prevent the app from exiting before closing the camera. */
+ private Semaphore cameraOpenCloseLock = new Semaphore(1);
+
+ /** A {@link CameraCaptureSession.CaptureCallback} that handles events related to capture. */
+ private CameraCaptureSession.CaptureCallback captureCallback =
+ new CameraCaptureSession.CaptureCallback() {
+
+ @Override
+ public void onCaptureProgressed(
+ @NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request,
+ @NonNull CaptureResult partialResult) {}
+
+ @Override
+ public void onCaptureCompleted(
+ @NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request,
+ @NonNull TotalCaptureResult result) {}
+ };
+
+ /**
+ * Shows a {@link Toast} on the UI thread for the classification results.
+ *
+ * @param text The message to show
+ */
+ private void showToast(final String text) {
+ final Activity activity = getActivity();
+ if (activity != null) {
+ activity.runOnUiThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ textView.setText(text);
+ }
+ });
+ }
+ }
+
+ /**
+ * Resizes image.
+ *
+ * Attempting to use too large a preview size could exceed the camera bus' bandwidth limitation,
+ * resulting in gorgeous previews but the storage of garbage capture data.
+ *
+ * Given {@code choices} of {@code Size}s supported by a camera, choose the smallest one that is
+ * at least as large as the respective texture view size, and that is at most as large as the
+ * respective max size, and whose aspect ratio matches with the specified value. If such size
+ * doesn't exist, choose the largest one that is at most as large as the respective max size, and
+ * whose aspect ratio matches with the specified value.
+ *
+ * @param choices The list of sizes that the camera supports for the intended output class
+ * @param textureViewWidth The width of the texture view relative to sensor coordinate
+ * @param textureViewHeight The height of the texture view relative to sensor coordinate
+ * @param maxWidth The maximum width that can be chosen
+ * @param maxHeight The maximum height that can be chosen
+ * @param aspectRatio The aspect ratio
+ * @return The optimal {@code Size}, or an arbitrary one if none were big enough
+ */
+ private static Size chooseOptimalSize(
+ Size[] choices,
+ int textureViewWidth,
+ int textureViewHeight,
+ int maxWidth,
+ int maxHeight,
+ Size aspectRatio) {
+
+ // Collect the supported resolutions that are at least as big as the preview Surface
+ List bigEnough = new ArrayList<>();
+ // Collect the supported resolutions that are smaller than the preview Surface
+ List notBigEnough = new ArrayList<>();
+ int w = aspectRatio.getWidth();
+ int h = aspectRatio.getHeight();
+ for (Size option : choices) {
+ if (option.getWidth() <= maxWidth
+ && option.getHeight() <= maxHeight
+ && option.getHeight() == option.getWidth() * h / w) {
+ if (option.getWidth() >= textureViewWidth && option.getHeight() >= textureViewHeight) {
+ bigEnough.add(option);
+ } else {
+ notBigEnough.add(option);
+ }
+ }
+ }
+
+ // Pick the smallest of those big enough. If there is no one big enough, pick the
+ // largest of those not big enough.
+ if (bigEnough.size() > 0) {
+ return Collections.min(bigEnough, new CompareSizesByArea());
+ } else if (notBigEnough.size() > 0) {
+ return Collections.max(notBigEnough, new CompareSizesByArea());
+ } else {
+ Log.e(TAG, "Couldn't find any suitable preview size");
+ return choices[0];
+ }
+ }
+
+ public static Camera2BasicFragment newInstance() {
+ return new Camera2BasicFragment();
+ }
+
+ /** Layout the preview and buttons. */
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_camera2_basic, container, false);
+ }
+
+ /** Connect the buttons to their event handler. */
+ @Override
+ public void onViewCreated(final View view, Bundle savedInstanceState) {
+ textureView = (AutoFitTextureView) view.findViewById(R.id.texture);
+ textView = (TextView) view.findViewById(R.id.text);
+ }
+
+ /** Load the model and labels. */
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ try {
+ classifier = new ImageClassifier(getActivity());
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to initialize an image classifier.");
+ }
+ startBackgroundThread();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ startBackgroundThread();
+
+ // When the screen is turned off and turned back on, the SurfaceTexture is already
+ // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
+ // a camera and start preview from here (otherwise, we wait until the surface is ready in
+ // the SurfaceTextureListener).
+ if (textureView.isAvailable()) {
+ openCamera(textureView.getWidth(), textureView.getHeight());
+ } else {
+ textureView.setSurfaceTextureListener(surfaceTextureListener);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ closeCamera();
+ stopBackgroundThread();
+ super.onPause();
+ }
+
+ @Override
+ public void onDestroy() {
+ classifier.close();
+ super.onDestroy();
+ }
+
+ /**
+ * Sets up member variables related to camera.
+ *
+ * @param width The width of available size for camera preview
+ * @param height The height of available size for camera preview
+ */
+ private void setUpCameraOutputs(int width, int height) {
+ Activity activity = getActivity();
+ CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
+ try {
+ for (String cameraId : manager.getCameraIdList()) {
+ CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
+
+ // We don't use a front facing camera in this sample.
+ Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
+ if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
+ continue;
+ }
+
+ StreamConfigurationMap map =
+ characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ if (map == null) {
+ continue;
+ }
+
+ // // For still image captures, we use the largest available size.
+ Size largest =
+ Collections.max(
+ Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new CompareSizesByArea());
+ imageReader =
+ ImageReader.newInstance(
+ largest.getWidth(), largest.getHeight(), ImageFormat.JPEG, /*maxImages*/ 2);
+
+ // Find out if we need to swap dimension to get the preview size relative to sensor
+ // coordinate.
+ int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
+ // noinspection ConstantConditions
+ /* Orientation of the camera sensor */
+ int sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
+ boolean swappedDimensions = false;
+ switch (displayRotation) {
+ case Surface.ROTATION_0:
+ case Surface.ROTATION_180:
+ if (sensorOrientation == 90 || sensorOrientation == 270) {
+ swappedDimensions = true;
+ }
+ break;
+ case Surface.ROTATION_90:
+ case Surface.ROTATION_270:
+ if (sensorOrientation == 0 || sensorOrientation == 180) {
+ swappedDimensions = true;
+ }
+ break;
+ default:
+ Log.e(TAG, "Display rotation is invalid: " + displayRotation);
+ }
+
+ Point displaySize = new Point();
+ activity.getWindowManager().getDefaultDisplay().getSize(displaySize);
+ int rotatedPreviewWidth = width;
+ int rotatedPreviewHeight = height;
+ int maxPreviewWidth = displaySize.x;
+ int maxPreviewHeight = displaySize.y;
+
+ if (swappedDimensions) {
+ rotatedPreviewWidth = height;
+ rotatedPreviewHeight = width;
+ maxPreviewWidth = displaySize.y;
+ maxPreviewHeight = displaySize.x;
+ }
+
+ if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {
+ maxPreviewWidth = MAX_PREVIEW_WIDTH;
+ }
+
+ if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
+ maxPreviewHeight = MAX_PREVIEW_HEIGHT;
+ }
+
+ previewSize =
+ chooseOptimalSize(
+ map.getOutputSizes(SurfaceTexture.class),
+ rotatedPreviewWidth,
+ rotatedPreviewHeight,
+ maxPreviewWidth,
+ maxPreviewHeight,
+ largest);
+
+ // We fit the aspect ratio of TextureView to the size of preview we picked.
+ int orientation = getResources().getConfiguration().orientation;
+ if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ textureView.setAspectRatio(previewSize.getWidth(), previewSize.getHeight());
+ } else {
+ textureView.setAspectRatio(previewSize.getHeight(), previewSize.getWidth());
+ }
+
+ this.cameraId = cameraId;
+ return;
+ }
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ } catch (NullPointerException e) {
+ // Currently an NPE is thrown when the Camera2API is used but not supported on the
+ // device this code runs.
+ ErrorDialog.newInstance(getString(R.string.camera_error))
+ .show(getChildFragmentManager(), FRAGMENT_DIALOG);
+ }
+ }
+
+ private String[] getRequiredPermissions() {
+ Activity activity = getActivity();
+ try {
+ PackageInfo info =
+ activity
+ .getPackageManager()
+ .getPackageInfo(activity.getPackageName(), PackageManager.GET_PERMISSIONS);
+ String[] ps = info.requestedPermissions;
+ if (ps != null && ps.length > 0) {
+ return ps;
+ } else {
+ return new String[0];
+ }
+ } catch (Exception e) {
+ return new String[0];
+ }
+ }
+
+ /** Opens the camera specified by {@link Camera2BasicFragment#cameraId}. */
+ private void openCamera(int width, int height) {
+ if (!checkedPermissions && !allPermissionsGranted()) {
+ FragmentCompat.requestPermissions(this, getRequiredPermissions(), PERMISSIONS_REQUEST_CODE);
+ return;
+ } else {
+ checkedPermissions = true;
+ }
+ setUpCameraOutputs(width, height);
+ configureTransform(width, height);
+ Activity activity = getActivity();
+ CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
+ try {
+ if (!cameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
+ throw new RuntimeException("Time out waiting to lock camera opening.");
+ }
+ manager.openCamera(cameraId, stateCallback, backgroundHandler);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
+ }
+ }
+
+ private boolean allPermissionsGranted() {
+ for (String permission : getRequiredPermissions()) {
+ if (ContextCompat.checkSelfPermission(getActivity(), permission)
+ != PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void onRequestPermissionsResult(
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+
+ /** Closes the current {@link CameraDevice}. */
+ private void closeCamera() {
+ try {
+ cameraOpenCloseLock.acquire();
+ if (null != captureSession) {
+ captureSession.close();
+ captureSession = null;
+ }
+ if (null != cameraDevice) {
+ cameraDevice.close();
+ cameraDevice = null;
+ }
+ if (null != imageReader) {
+ imageReader.close();
+ imageReader = null;
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
+ } finally {
+ cameraOpenCloseLock.release();
+ }
+ }
+
+ /** Starts a background thread and its {@link Handler}. */
+ private void startBackgroundThread() {
+ backgroundThread = new HandlerThread(HANDLE_THREAD_NAME);
+ backgroundThread.start();
+ backgroundHandler = new Handler(backgroundThread.getLooper());
+ synchronized (lock) {
+ runClassifier = true;
+ }
+ backgroundHandler.post(periodicClassify);
+ }
+
+ /** Stops the background thread and its {@link Handler}. */
+ private void stopBackgroundThread() {
+ backgroundThread.quitSafely();
+ try {
+ backgroundThread.join();
+ backgroundThread = null;
+ backgroundHandler = null;
+ synchronized (lock) {
+ runClassifier = false;
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /** Takes photos and classify them periodically. */
+ private Runnable periodicClassify =
+ new Runnable() {
+ @Override
+ public void run() {
+ synchronized (lock) {
+ if (runClassifier) {
+ classifyFrame();
+ }
+ }
+ backgroundHandler.post(periodicClassify);
+ }
+ };
+
+ /** Creates a new {@link CameraCaptureSession} for camera preview. */
+ private void createCameraPreviewSession() {
+ try {
+ SurfaceTexture texture = textureView.getSurfaceTexture();
+ assert texture != null;
+
+ // We configure the size of default buffer to be the size of camera preview we want.
+ texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+
+ // This is the output Surface we need to start preview.
+ Surface surface = new Surface(texture);
+
+ // We set up a CaptureRequest.Builder with the output Surface.
+ previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ previewRequestBuilder.addTarget(surface);
+
+ // Here, we create a CameraCaptureSession for camera preview.
+ cameraDevice.createCaptureSession(
+ Arrays.asList(surface),
+ new CameraCaptureSession.StateCallback() {
+
+ @Override
+ public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
+ // The camera is already closed
+ if (null == cameraDevice) {
+ return;
+ }
+
+ // When the session is ready, we start displaying the preview.
+ captureSession = cameraCaptureSession;
+ try {
+ // Auto focus should be continuous for camera preview.
+ previewRequestBuilder.set(
+ CaptureRequest.CONTROL_AF_MODE,
+ CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+
+ // Finally, we start displaying the camera preview.
+ previewRequest = previewRequestBuilder.build();
+ captureSession.setRepeatingRequest(
+ previewRequest, captureCallback, backgroundHandler);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
+ showToast("Failed");
+ }
+ },
+ null);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Configures the necessary {@link android.graphics.Matrix} transformation to `textureView`. This
+ * method should be called after the camera preview size is determined in setUpCameraOutputs and
+ * also the size of `textureView` is fixed.
+ *
+ * @param viewWidth The width of `textureView`
+ * @param viewHeight The height of `textureView`
+ */
+ private void configureTransform(int viewWidth, int viewHeight) {
+ Activity activity = getActivity();
+ if (null == textureView || null == previewSize || null == activity) {
+ return;
+ }
+ int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
+ Matrix matrix = new Matrix();
+ RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
+ RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth());
+ float centerX = viewRect.centerX();
+ float centerY = viewRect.centerY();
+ if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
+ bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
+ matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
+ float scale =
+ Math.max(
+ (float) viewHeight / previewSize.getHeight(),
+ (float) viewWidth / previewSize.getWidth());
+ matrix.postScale(scale, scale, centerX, centerY);
+ matrix.postRotate(90 * (rotation - 2), centerX, centerY);
+ } else if (Surface.ROTATION_180 == rotation) {
+ matrix.postRotate(180, centerX, centerY);
+ }
+ textureView.setTransform(matrix);
+ }
+
+ /** Classifies a frame from the preview stream. */
+ private void classifyFrame() {
+ if (classifier == null || getActivity() == null || cameraDevice == null) {
+ showToast("Uninitialized Classifier or invalid context.");
+ return;
+ }
+ Bitmap bitmap =
+ textureView.getBitmap(ImageClassifier.DIM_IMG_SIZE_X, ImageClassifier.DIM_IMG_SIZE_Y);
+ String textToShow = classifier.classifyFrame(bitmap);
+ bitmap.recycle();
+ showToast(textToShow);
+ }
+
+ /** Compares two {@code Size}s based on their areas. */
+ private static class CompareSizesByArea implements Comparator {
+
+ @Override
+ public int compare(Size lhs, Size rhs) {
+ // We cast here to ensure the multiplications won't overflow
+ return Long.signum(
+ (long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight());
+ }
+ }
+
+ /** Shows an error message dialog. */
+ public static class ErrorDialog extends DialogFragment {
+
+ private static final String ARG_MESSAGE = "message";
+
+ public static ErrorDialog newInstance(String message) {
+ ErrorDialog dialog = new ErrorDialog();
+ Bundle args = new Bundle();
+ args.putString(ARG_MESSAGE, message);
+ dialog.setArguments(args);
+ return dialog;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Activity activity = getActivity();
+ return new AlertDialog.Builder(activity)
+ .setMessage(getArguments().getString(ARG_MESSAGE))
+ .setPositiveButton(
+ android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ activity.finish();
+ }
+ })
+ .create();
+ }
+ }
+}
diff --git a/android-tflite/app/src/main/java/com/example/android/tflitecamerademo/CameraActivity.java b/android-tflite/app/src/main/java/com/example/android/tflitecamerademo/CameraActivity.java
new file mode 100755
index 0000000..e7161dd
--- /dev/null
+++ b/android-tflite/app/src/main/java/com/example/android/tflitecamerademo/CameraActivity.java
@@ -0,0 +1,35 @@
+/* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+package com.example.android.tflitecamerademo;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/** Main {@code Activity} class for the Camera app. */
+public class CameraActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_camera);
+ if (null == savedInstanceState) {
+ getFragmentManager()
+ .beginTransaction()
+ .replace(R.id.container, Camera2BasicFragment.newInstance())
+ .commit();
+ }
+ }
+}
diff --git a/android-tflite/app/src/main/java/com/example/android/tflitecamerademo/ImageClassifier.java b/android-tflite/app/src/main/java/com/example/android/tflitecamerademo/ImageClassifier.java
new file mode 100755
index 0000000..d3354a2
--- /dev/null
+++ b/android-tflite/app/src/main/java/com/example/android/tflitecamerademo/ImageClassifier.java
@@ -0,0 +1,222 @@
+/* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+package com.example.android.tflitecamerademo;
+
+import android.app.Activity;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.Bitmap;
+import android.os.SystemClock;
+import android.util.Log;
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.PriorityQueue;
+import org.tensorflow.lite.Interpreter;
+
+/** Classifies images with Tensorflow Lite. */
+public class ImageClassifier {
+
+ /** Tag for the {@link Log}. */
+ private static final String TAG = "TfLiteCameraDemo";
+
+ /** Name of the model file stored in Assets. */
+ private static final String MODEL_PATH = "graph.lite";
+
+ /** Name of the label file stored in Assets. */
+ private static final String LABEL_PATH = "labels.txt";
+
+ /** Number of results to show in the UI. */
+ private static final int RESULTS_TO_SHOW = 3;
+
+ /** Dimensions of inputs. */
+ private static final int DIM_BATCH_SIZE = 1;
+
+ private static final int DIM_PIXEL_SIZE = 3;
+
+ static final int DIM_IMG_SIZE_X = 224;
+ static final int DIM_IMG_SIZE_Y = 224;
+
+ private static final int IMAGE_MEAN = 128;
+ private static final float IMAGE_STD = 128.0f;
+
+
+ /* Preallocated buffers for storing image data in. */
+ private int[] intValues = new int[DIM_IMG_SIZE_X * DIM_IMG_SIZE_Y];
+
+ /** An instance of the driver class to run model inference with Tensorflow Lite. */
+ private Interpreter tflite;
+
+ /** Labels corresponding to the output of the vision model. */
+ private List labelList;
+
+ /** A ByteBuffer to hold image data, to be feed into Tensorflow Lite as inputs. */
+ private ByteBuffer imgData = null;
+
+ /** An array to hold inference results, to be feed into Tensorflow Lite as outputs. */
+ private float[][] labelProbArray = null;
+ /** multi-stage low pass filter **/
+ private float[][] filterLabelProbArray = null;
+ private static final int FILTER_STAGES = 3;
+ private static final float FILTER_FACTOR = 0.4f;
+
+ private PriorityQueue> sortedLabels =
+ new PriorityQueue<>(
+ RESULTS_TO_SHOW,
+ new Comparator>() {
+ @Override
+ public int compare(Map.Entry o1, Map.Entry o2) {
+ return (o1.getValue()).compareTo(o2.getValue());
+ }
+ });
+
+ /** Initializes an {@code ImageClassifier}. */
+ ImageClassifier(Activity activity) throws IOException {
+ tflite = new Interpreter(loadModelFile(activity));
+ labelList = loadLabelList(activity);
+ imgData =
+ ByteBuffer.allocateDirect(
+ 4 * DIM_BATCH_SIZE * DIM_IMG_SIZE_X * DIM_IMG_SIZE_Y * DIM_PIXEL_SIZE);
+ imgData.order(ByteOrder.nativeOrder());
+ labelProbArray = new float[1][labelList.size()];
+ filterLabelProbArray = new float[FILTER_STAGES][labelList.size()];
+ Log.d(TAG, "Created a Tensorflow Lite Image Classifier.");
+ }
+
+ /** Classifies a frame from the preview stream. */
+ String classifyFrame(Bitmap bitmap) {
+ if (tflite == null) {
+ Log.e(TAG, "Image classifier has not been initialized; Skipped.");
+ return "Uninitialized Classifier.";
+ }
+ convertBitmapToByteBuffer(bitmap);
+ // Here's where the magic happens!!!
+ long startTime = SystemClock.uptimeMillis();
+ tflite.run(imgData, labelProbArray);
+ long endTime = SystemClock.uptimeMillis();
+ Log.d(TAG, "Timecost to run model inference: " + Long.toString(endTime - startTime));
+
+ // smooth the results
+ applyFilter();
+
+ // print the results
+ String textToShow = printTopKLabels();
+ textToShow = Long.toString(endTime - startTime) + "ms" + textToShow;
+ return textToShow;
+ }
+
+ void applyFilter(){
+ int num_labels = labelList.size();
+
+ // Low pass filter `labelProbArray` into the first stage of the filter.
+ for(int j=0; j loadLabelList(Activity activity) throws IOException {
+ List labelList = new ArrayList();
+ BufferedReader reader =
+ new BufferedReader(new InputStreamReader(activity.getAssets().open(LABEL_PATH)));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ labelList.add(line);
+ }
+ reader.close();
+ return labelList;
+ }
+
+ /** Memory-map the model file in Assets. */
+ private MappedByteBuffer loadModelFile(Activity activity) throws IOException {
+ AssetFileDescriptor fileDescriptor = activity.getAssets().openFd(MODEL_PATH);
+ FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());
+ FileChannel fileChannel = inputStream.getChannel();
+ long startOffset = fileDescriptor.getStartOffset();
+ long declaredLength = fileDescriptor.getDeclaredLength();
+ return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength);
+ }
+
+ /** Writes Image data into a {@code ByteBuffer}. */
+ private void convertBitmapToByteBuffer(Bitmap bitmap) {
+ if (imgData == null) {
+ return;
+ }
+ imgData.rewind();
+ bitmap.getPixels(intValues, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
+ // Convert the image to floating point.
+ int pixel = 0;
+ long startTime = SystemClock.uptimeMillis();
+ for (int i = 0; i < DIM_IMG_SIZE_X; ++i) {
+ for (int j = 0; j < DIM_IMG_SIZE_Y; ++j) {
+ final int val = intValues[pixel++];
+ imgData.putFloat((((val >> 16) & 0xFF)-IMAGE_MEAN)/IMAGE_STD);
+ imgData.putFloat((((val >> 8) & 0xFF)-IMAGE_MEAN)/IMAGE_STD);
+ imgData.putFloat((((val) & 0xFF)-IMAGE_MEAN)/IMAGE_STD);
+ }
+ }
+ long endTime = SystemClock.uptimeMillis();
+ Log.d(TAG, "Timecost to put values into ByteBuffer: " + Long.toString(endTime - startTime));
+ }
+
+ /** Prints top-K labels, to be shown in UI as the results. */
+ private String printTopKLabels() {
+ for (int i = 0; i < labelList.size(); ++i) {
+ sortedLabels.add(
+ new AbstractMap.SimpleEntry<>(labelList.get(i), labelProbArray[0][i]));
+ if (sortedLabels.size() > RESULTS_TO_SHOW) {
+ sortedLabels.poll();
+ }
+ }
+ String textToShow = "";
+ final int size = sortedLabels.size();
+ for (int i = 0; i < size; ++i) {
+ Map.Entry label = sortedLabels.poll();
+ textToShow = String.format("\n%s: %4.2f",label.getKey(),label.getValue()) + textToShow;
+ }
+ return textToShow;
+ }
+}
diff --git a/android-tflite/app/src/main/res/drawable-hdpi/ic_action_info.png b/android-tflite/app/src/main/res/drawable-hdpi/ic_action_info.png
new file mode 100755
index 0000000..e0a7000
Binary files /dev/null and b/android-tflite/app/src/main/res/drawable-hdpi/ic_action_info.png differ
diff --git a/android-tflite/app/src/main/res/drawable-hdpi/ic_launcher.png b/android-tflite/app/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100755
index 0000000..c22509d
Binary files /dev/null and b/android-tflite/app/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/android-tflite/app/src/main/res/drawable-hdpi/tile.9.png b/android-tflite/app/src/main/res/drawable-hdpi/tile.9.png
new file mode 100755
index 0000000..a84e3ef
Binary files /dev/null and b/android-tflite/app/src/main/res/drawable-hdpi/tile.9.png differ
diff --git a/android-tflite/app/src/main/res/drawable-mdpi/ic_action_info.png b/android-tflite/app/src/main/res/drawable-mdpi/ic_action_info.png
new file mode 100755
index 0000000..520c2dd
Binary files /dev/null and b/android-tflite/app/src/main/res/drawable-mdpi/ic_action_info.png differ
diff --git a/android-tflite/app/src/main/res/drawable-mdpi/ic_launcher.png b/android-tflite/app/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100755
index 0000000..d68af39
Binary files /dev/null and b/android-tflite/app/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/android-tflite/app/src/main/res/drawable-xhdpi/ic_action_info.png b/android-tflite/app/src/main/res/drawable-xhdpi/ic_action_info.png
new file mode 100755
index 0000000..1347b09
Binary files /dev/null and b/android-tflite/app/src/main/res/drawable-xhdpi/ic_action_info.png differ
diff --git a/android-tflite/app/src/main/res/drawable-xhdpi/ic_launcher.png b/android-tflite/app/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100755
index 0000000..15e419b
Binary files /dev/null and b/android-tflite/app/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/android-tflite/app/src/main/res/drawable-xxhdpi/ic_action_info.png b/android-tflite/app/src/main/res/drawable-xxhdpi/ic_action_info.png
new file mode 100755
index 0000000..fd93333
Binary files /dev/null and b/android-tflite/app/src/main/res/drawable-xxhdpi/ic_action_info.png differ
diff --git a/android-tflite/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/android-tflite/app/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100755
index 0000000..342ce34
Binary files /dev/null and b/android-tflite/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/android-tflite/app/src/main/res/layout-land/fragment_camera2_basic.xml b/android-tflite/app/src/main/res/layout-land/fragment_camera2_basic.xml
new file mode 100755
index 0000000..a84f1bb
--- /dev/null
+++ b/android-tflite/app/src/main/res/layout-land/fragment_camera2_basic.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android-tflite/app/src/main/res/layout/activity_camera.xml b/android-tflite/app/src/main/res/layout/activity_camera.xml
new file mode 100755
index 0000000..286e549
--- /dev/null
+++ b/android-tflite/app/src/main/res/layout/activity_camera.xml
@@ -0,0 +1,22 @@
+
+
diff --git a/android-tflite/app/src/main/res/layout/fragment_camera2_basic.xml b/android-tflite/app/src/main/res/layout/fragment_camera2_basic.xml
new file mode 100755
index 0000000..15305c4
--- /dev/null
+++ b/android-tflite/app/src/main/res/layout/fragment_camera2_basic.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android-tflite/app/src/main/res/values-sw600dp/template-dimens.xml b/android-tflite/app/src/main/res/values-sw600dp/template-dimens.xml
new file mode 100755
index 0000000..22074a2
--- /dev/null
+++ b/android-tflite/app/src/main/res/values-sw600dp/template-dimens.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ @dimen/margin_huge
+ @dimen/margin_medium
+
+
diff --git a/android-tflite/app/src/main/res/values-sw600dp/template-styles.xml b/android-tflite/app/src/main/res/values-sw600dp/template-styles.xml
new file mode 100755
index 0000000..03d1974
--- /dev/null
+++ b/android-tflite/app/src/main/res/values-sw600dp/template-styles.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/android-tflite/app/src/main/res/values-v11/template-styles.xml b/android-tflite/app/src/main/res/values-v11/template-styles.xml
new file mode 100755
index 0000000..8c1ea66
--- /dev/null
+++ b/android-tflite/app/src/main/res/values-v11/template-styles.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
diff --git a/android-tflite/app/src/main/res/values-v21/base-colors.xml b/android-tflite/app/src/main/res/values-v21/base-colors.xml
new file mode 100755
index 0000000..8b6ec3f
--- /dev/null
+++ b/android-tflite/app/src/main/res/values-v21/base-colors.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/android-tflite/app/src/main/res/values-v21/base-template-styles.xml b/android-tflite/app/src/main/res/values-v21/base-template-styles.xml
new file mode 100755
index 0000000..c778e4f
--- /dev/null
+++ b/android-tflite/app/src/main/res/values-v21/base-template-styles.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
diff --git a/android-tflite/app/src/main/res/values/base-strings.xml b/android-tflite/app/src/main/res/values/base-strings.xml
new file mode 100755
index 0000000..0564dfd
--- /dev/null
+++ b/android-tflite/app/src/main/res/values/base-strings.xml
@@ -0,0 +1,30 @@
+
+
+
+
+ TensorDex
+
+
+
+
diff --git a/android-tflite/app/src/main/res/values/colors.xml b/android-tflite/app/src/main/res/values/colors.xml
new file mode 100755
index 0000000..4b75d2b
--- /dev/null
+++ b/android-tflite/app/src/main/res/values/colors.xml
@@ -0,0 +1,19 @@
+
+
+
+ #cc4285f4
+
diff --git a/android-tflite/app/src/main/res/values/strings.xml b/android-tflite/app/src/main/res/values/strings.xml
new file mode 100755
index 0000000..a08ec3e
--- /dev/null
+++ b/android-tflite/app/src/main/res/values/strings.xml
@@ -0,0 +1,24 @@
+
+
+ Picture
+ Info
+ This sample needs camera permission.
+ This device doesn\'t support Camera2 API.
+ NN:On
+ NN:Off
+ Use NNAPI
+
diff --git a/android-tflite/app/src/main/res/values/styles.xml b/android-tflite/app/src/main/res/values/styles.xml
new file mode 100755
index 0000000..3f3bdfb
--- /dev/null
+++ b/android-tflite/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
diff --git a/android-tflite/app/src/main/res/values/template-dimens.xml b/android-tflite/app/src/main/res/values/template-dimens.xml
new file mode 100755
index 0000000..39e710b
--- /dev/null
+++ b/android-tflite/app/src/main/res/values/template-dimens.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+ 4dp
+ 8dp
+ 16dp
+ 32dp
+ 64dp
+
+
+
+ @dimen/margin_medium
+ @dimen/margin_medium
+
+
diff --git a/android-tflite/app/src/main/res/values/template-styles.xml b/android-tflite/app/src/main/res/values/template-styles.xml
new file mode 100755
index 0000000..6e7d593
--- /dev/null
+++ b/android-tflite/app/src/main/res/values/template-styles.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android-tflite/build.gradle b/android-tflite/build.gradle
new file mode 100755
index 0000000..151ee7f
--- /dev/null
+++ b/android-tflite/build.gradle
@@ -0,0 +1,24 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ google()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.3.2'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/android-tflite/gradle.properties b/android-tflite/gradle.properties
new file mode 100755
index 0000000..aac7c9b
--- /dev/null
+++ b/android-tflite/gradle.properties
@@ -0,0 +1,17 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/android-tflite/gradle/wrapper/gradle-wrapper.properties b/android-tflite/gradle/wrapper/gradle-wrapper.properties
index 4d87136..e2bdfe9 100755
--- a/android-tflite/gradle/wrapper/gradle-wrapper.properties
+++ b/android-tflite/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Tue Nov 14 09:09:45 PST 2017
+#Sun Apr 14 18:55:46 CDT 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
diff --git a/android-tflite/settings.gradle b/android-tflite/settings.gradle
new file mode 100755
index 0000000..e7b4def
--- /dev/null
+++ b/android-tflite/settings.gradle
@@ -0,0 +1 @@
+include ':app'