├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── .travis.yml ├── README.md ├── VisionRecognition.iml ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── synset_words.txt~ │ ├── java │ └── com │ │ └── tzutalin │ │ └── vision │ │ └── demo │ │ ├── AutoFitTextureView.java │ │ ├── Camera2BasicFragment.java │ │ ├── CameraActivity.java │ │ ├── ObjectDetectActivity.java │ │ └── SceneRecognitionActivity.java │ └── res │ ├── drawable-hdpi │ ├── ic_action_info.png │ ├── ic_launcher.png │ └── tile.9.png │ ├── drawable-mdpi │ ├── ic_action_info.png │ └── ic_launcher.png │ ├── drawable-xhdpi │ ├── ic_action_info.png │ └── ic_launcher.png │ ├── drawable-xxhdpi │ ├── ic_action_info.png │ └── ic_launcher.png │ ├── drawable │ └── ic_launcher.png │ ├── layout-land │ └── fragment_camera2_basic.xml │ ├── layout │ ├── activity_camera.xml │ ├── activity_main.xml │ ├── activity_object_detect.xml │ ├── activity_scene_recognition.xml │ └── fragment_camera2_basic.xml │ ├── menu │ ├── menu_main.xml │ ├── menu_object_detect.xml │ └── menu_scene_recognition.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── values-sw600dp │ └── template-styles.xml │ ├── values-v11 │ └── template-styles.xml │ ├── values-v21 │ ├── base-colors.xml │ ├── base-template-styles.xml │ └── styles.xml │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ ├── styles.xml │ ├── template-dimens.xml │ └── template-styles.xml ├── build.gradle ├── cnnlibs ├── .gitignore ├── build.gradle ├── cnnlibs.iml ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── tzutalin │ │ └── vision │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── tzutalin │ │ └── vision │ │ └── visionrecognition │ │ ├── CaffeClassifier.java │ │ ├── ObjectDetector.java │ │ ├── SceneClassifier.java │ │ ├── Utils.java │ │ ├── VisionClassifierCreator.java │ │ └── VisionDetRet.java │ ├── jniLibs │ ├── armeabi-v7a │ │ ├── libobjrek.so │ │ └── libobjrek_jni.so │ └── x86 │ │ ├── libobjrek.so │ │ └── libobjrek_jni.so │ └── res │ └── values │ └── strings.xml ├── contributors.txt ├── demo ├── 1.png ├── 2.png ├── 3.png ├── 4.png └── 5.png ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle ├── setup.sh └── tools ├── get_model.py └── testcar.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | /captures 8 | *.swp 9 | phone_data 10 | /projectFilesBackup/ 11 | *.iml 12 | .gradle 13 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Android-Object-Detection -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.8 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | 3 | script: "./gradlew assembleDebug" 4 | 5 | jdk: 6 | - oraclejdk8 7 | 8 | android: 9 | components: 10 | # The BuildTools version used by your project 11 | - tools 12 | - platform-tools 13 | - build-tools-25.0.0 14 | # The SDK version used to compile your project 15 | - android-25 16 | - extra-android-m2repository 17 | - extra-android-support 18 | # Additional components 19 | #- extra-android-m2repository 20 | licenses: 21 | - android-sdk-license-.+ 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android-Object-Detection 2 | [![Build Status](https://travis-ci.org/tzutalin/Android-Object-Detection.png)](https://travis-ci.org/tzutalin/Android-Object-Detection) 3 | 4 | ## Requirements 5 | * Android 4.0+ support 6 | 7 | * ARMv7 and x86 based devices 8 | 9 | * Get the Caffe model and push it to Phone SDCard. For object detection, network(*.prototxt) should use ROILayer, you can refer to [Fast-RCNN](https://github.com/rbgirshick/fast-rcnn). For scene recognition(object recognition), it can use any caffe network and weight with memory input layer. 10 | 11 | * Build with Gradle. You can use Android studio to build 12 | 13 | ## Feature 14 | * [Object detection - Region-based Convolutional Networks detection](http://arxiv.org/abs/1504.08083) 15 | [Selective Search on Android](https://github.com/tzutalin/dlib-android) + [FastRCNN](https://github.com/rbgirshick/caffe-fast-rcnn) 16 | 17 | * [Scene recognition - Convolutional neural networks trained on Places](http://places.csail.mit.edu/downloadCNN.html) 18 | Input a picture of a place or scene and predicts it. 19 | 20 | ## Demo 21 | 22 | ![](demo/1.png) 23 | 24 | ![](demo/2.png) 25 | 26 | ![](demo/3.png) 27 | 28 | ![](demo/4.png) 29 | 30 | ![](demo/5.png) 31 | 32 | ## Usage 33 | 34 | * Download and push the neccessary file to your phone. There should be model and weight in /sdcard/fastrcnn and /sdcard/vision_scene. 35 | 36 | ` $ ./setup.sh ` 37 | 38 | * Build and run the application using gradlew or you can open AndroidStudio to import this project 39 | 40 | ` $ ./gradlew assembleDebug` 41 | 42 | ` $ adb install -r ./app/build/outputs/apk` 43 | 44 | Besides, you can change deep learning's model, weight, etc in VisionClassifierCreator.java 45 | ``` java 46 | 47 | public class VisionClassifierCreator { 48 | private final static String SCENE_MODEL_PATH = ".."; 49 | private final static String SCENE_WIEGHTS_PATH = ".."; 50 | private final static String SCENE_MEAN_FILE = ".."; 51 | private final static String SCENE_SYNSET_FILE = ".."; 52 | 53 | private final static String DETECT_MODEL_PATH = ".."; 54 | private final static String DETECT_WIEGHTS_PATH = ".."; 55 | private final static String DETECT_MEAN_FILE = ".."; 56 | private final static String DETECT_SYNSET_FILE = ".."; 57 | } 58 | ``` 59 | 60 | ## Contribution 61 | * Send pull request 62 | * Buy Me a Coffee at ko-fi.com 63 | 64 | ## License 65 | 66 | Copyright (C) 2015-2016 TzuTaLin 67 | 68 | Licensed under the Apache License, Version 2.0 (the "License"); 69 | you may not use this file except in compliance with the License. 70 | You may obtain a copy of the License at 71 | 72 | http://www.apache.org/licenses/LICENSE-2.0 73 | 74 | Unless required by applicable law or agreed to in writing, software 75 | distributed under the License is distributed on an "AS IS" BASIS, 76 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 77 | See the License for the specific language governing permissions and 78 | limitations under the License. 79 | -------------------------------------------------------------------------------- /VisionRecognition.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.androidCompileSdkVersion 5 | buildToolsVersion "${rootProject.ext.androidBuildToolsVersion}" 6 | 7 | defaultConfig { 8 | applicationId "com.tzutalin.vision.visionrecognition" 9 | minSdkVersion rootProject.ext.minSdkVersion 10 | targetSdkVersion rootProject.ext.targetSdkVersion 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | 21 | dependencies { 22 | compile "com.android.support:support-v4:${rootProject.ext.androidSupportSdkVersion}" 23 | compile "com.android.support:support-v13:${rootProject.ext.androidSupportSdkVersion}" 24 | compile "com.android.support:cardview-v7:${rootProject.ext.androidSupportSdkVersion}" 25 | compile "com.android.support:appcompat-v7:${rootProject.ext.androidSupportSdkVersion}" 26 | compile "com.github.dexafree:materiallist:3.0.1" 27 | compile project(':cnnlibs') 28 | } 29 | } 30 | 31 | dependencies { 32 | compile fileTree(dir: 'libs', include: ['*.jar']) 33 | compile "com.android.support:support-annotations:${rootProject.ext.androidSupportSdkVersion}" 34 | } 35 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/darrenl/tools/android-sdk-linux/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 31 | 32 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/assets/synset_words.txt~: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzutalin/Android-Object-Detection/ff0319803e382cb9b7037bc49e38d7e33369d568/app/src/main/assets/synset_words.txt~ -------------------------------------------------------------------------------- /app/src/main/java/com/tzutalin/vision/demo/AutoFitTextureView.java: -------------------------------------------------------------------------------- 1 | package com.tzutalin.vision.demo; 2 | 3 | /** 4 | * Created by darrenl on 2015/9/10. 5 | */ 6 | import android.content.Context; 7 | import android.util.AttributeSet; 8 | import android.view.TextureView; 9 | 10 | /** 11 | * A {@link TextureView} that opencan be adjusted to a specified aspect ratio. 12 | */ 13 | public class AutoFitTextureView extends TextureView { 14 | 15 | private int mRatioWidth; 16 | private int mRatioHeight; 17 | 18 | public AutoFitTextureView(Context context) { 19 | this(context, null); 20 | } 21 | 22 | public AutoFitTextureView(Context context, AttributeSet attrs) { 23 | this(context, attrs, 0); 24 | } 25 | 26 | public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) { 27 | super(context, attrs, defStyle); 28 | } 29 | 30 | /** 31 | * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio 32 | * calculated from the parameters. Note that the actual sizes of parameters don't matter, that 33 | * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result. 34 | * 35 | * @param width Relative horizontal size 36 | * @param height Relative vertical size 37 | */ 38 | public void setAspectRatio(int width, int height) { 39 | if (width < 0 || height < 0) { 40 | throw new IllegalArgumentException("Size cannot be negative."); 41 | } 42 | mRatioWidth = width; 43 | mRatioHeight = height; 44 | requestLayout(); 45 | } 46 | 47 | @Override 48 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 49 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 50 | int width = MeasureSpec.getSize(widthMeasureSpec); 51 | int height = MeasureSpec.getSize(heightMeasureSpec); 52 | if (0 == mRatioWidth || 0 == mRatioHeight) { 53 | setMeasuredDimension(width, height); 54 | } else { 55 | if (width < height * mRatioWidth / mRatioHeight) { 56 | setMeasuredDimension(width, width * mRatioHeight / mRatioWidth); 57 | } else { 58 | setMeasuredDimension(height * mRatioWidth / mRatioHeight, height); 59 | } 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/tzutalin/vision/demo/Camera2BasicFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 TzuTaLin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.tzutalin.vision.demo; 17 | 18 | import android.Manifest; 19 | import android.app.Activity; 20 | import android.app.AlertDialog; 21 | import android.app.Dialog; 22 | import android.app.DialogFragment; 23 | import android.app.Fragment; 24 | import android.content.Context; 25 | import android.content.DialogInterface; 26 | import android.content.Intent; 27 | import android.content.pm.PackageManager; 28 | import android.content.res.Configuration; 29 | import android.graphics.ImageFormat; 30 | import android.graphics.Matrix; 31 | import android.graphics.RectF; 32 | import android.graphics.SurfaceTexture; 33 | import android.hardware.camera2.CameraAccessException; 34 | import android.hardware.camera2.CameraCaptureSession; 35 | import android.hardware.camera2.CameraCharacteristics; 36 | import android.hardware.camera2.CameraDevice; 37 | import android.hardware.camera2.CameraManager; 38 | import android.hardware.camera2.CameraMetadata; 39 | import android.hardware.camera2.CaptureRequest; 40 | import android.hardware.camera2.CaptureResult; 41 | import android.hardware.camera2.TotalCaptureResult; 42 | import android.hardware.camera2.params.StreamConfigurationMap; 43 | import android.media.Image; 44 | import android.media.ImageReader; 45 | import android.os.Bundle; 46 | import android.os.Handler; 47 | import android.os.HandlerThread; 48 | import android.support.annotation.NonNull; 49 | import android.support.v13.app.FragmentCompat; 50 | import android.util.Log; 51 | import android.util.Size; 52 | import android.util.SparseIntArray; 53 | import android.view.LayoutInflater; 54 | import android.view.Surface; 55 | import android.view.TextureView; 56 | import android.view.View; 57 | import android.view.ViewGroup; 58 | import android.widget.Toast; 59 | 60 | import com.tzutalin.vision.visionrecognition.R; 61 | 62 | import java.io.File; 63 | import java.io.FileOutputStream; 64 | import java.io.IOException; 65 | import java.nio.ByteBuffer; 66 | import java.util.ArrayList; 67 | import java.util.Arrays; 68 | import java.util.Collections; 69 | import java.util.Comparator; 70 | import java.util.List; 71 | import java.util.concurrent.Semaphore; 72 | import java.util.concurrent.TimeUnit; 73 | 74 | public class Camera2BasicFragment extends Fragment 75 | implements View.OnClickListener, FragmentCompat.OnRequestPermissionsResultCallback { 76 | 77 | /** 78 | * Conversion from screen rotation to JPEG orientation. 79 | */ 80 | private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); 81 | private static final int REQUEST_CAMERA_PERMISSION = 1; 82 | private static final String FRAGMENT_DIALOG = "dialog"; 83 | public static final String KEY_IMGPATH = "KEY_IMGPATH"; 84 | 85 | static { 86 | ORIENTATIONS.append(Surface.ROTATION_0, 90); 87 | ORIENTATIONS.append(Surface.ROTATION_90, 0); 88 | ORIENTATIONS.append(Surface.ROTATION_180, 270); 89 | ORIENTATIONS.append(Surface.ROTATION_270, 180); 90 | } 91 | 92 | enum VisionAction { ObjDetect, SceneRecognition} 93 | 94 | VisionAction mAction; 95 | /** 96 | * Tag for the {@link Log}. 97 | */ 98 | private static final String TAG = "Camera2BasicFragment"; 99 | 100 | /** 101 | * Camera state: Showing camera preview. 102 | */ 103 | private static final int STATE_PREVIEW = 0; 104 | 105 | /** 106 | * Camera state: Waiting for the focus to be locked. 107 | */ 108 | private static final int STATE_WAITING_LOCK = 1; 109 | 110 | /** 111 | * Camera state: Waiting for the exposure to be precapture state. 112 | */ 113 | private static final int STATE_WAITING_PRECAPTURE = 2; 114 | 115 | /** 116 | * Camera state: Waiting for the exposure state to be something other than precapture. 117 | */ 118 | private static final int STATE_WAITING_NON_PRECAPTURE = 3; 119 | 120 | /** 121 | * Camera state: Picture was taken. 122 | */ 123 | private static final int STATE_PICTURE_TAKEN = 4; 124 | 125 | /** 126 | * {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a 127 | * {@link TextureView}. 128 | */ 129 | private final TextureView.SurfaceTextureListener mSurfaceTextureListener 130 | = new TextureView.SurfaceTextureListener() { 131 | 132 | @Override 133 | public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) { 134 | openCamera(width, height); 135 | } 136 | 137 | @Override 138 | public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) { 139 | configureTransform(width, height); 140 | } 141 | 142 | @Override 143 | public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) { 144 | return true; 145 | } 146 | 147 | @Override 148 | public void onSurfaceTextureUpdated(SurfaceTexture texture) { 149 | } 150 | 151 | }; 152 | 153 | /** 154 | * ID of the current {@link CameraDevice}. 155 | */ 156 | private String mCameraId; 157 | 158 | /** 159 | * An {@link AutoFitTextureView} for camera preview. 160 | */ 161 | private AutoFitTextureView mTextureView; 162 | 163 | /** 164 | * A {@link CameraCaptureSession } for camera preview. 165 | */ 166 | private CameraCaptureSession mCaptureSession; 167 | 168 | /** 169 | * A reference to the opened {@link CameraDevice}. 170 | */ 171 | private CameraDevice mCameraDevice; 172 | 173 | /** 174 | * The {@link android.util.Size} of camera preview. 175 | */ 176 | private Size mPreviewSize; 177 | 178 | /** 179 | * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state. 180 | */ 181 | private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { 182 | 183 | @Override 184 | public void onOpened(@NonNull CameraDevice cameraDevice) { 185 | // This method is called when the camera is opened. We start camera preview here. 186 | mCameraOpenCloseLock.release(); 187 | mCameraDevice = cameraDevice; 188 | createCameraPreviewSession(); 189 | } 190 | 191 | @Override 192 | public void onDisconnected(@NonNull CameraDevice cameraDevice) { 193 | mCameraOpenCloseLock.release(); 194 | cameraDevice.close(); 195 | mCameraDevice = null; 196 | } 197 | 198 | @Override 199 | public void onError(@NonNull CameraDevice cameraDevice, int error) { 200 | mCameraOpenCloseLock.release(); 201 | cameraDevice.close(); 202 | mCameraDevice = null; 203 | Activity activity = getActivity(); 204 | if (null != activity) { 205 | activity.finish(); 206 | } 207 | } 208 | 209 | }; 210 | 211 | /** 212 | * An additional thread for running tasks that shouldn't block the UI. 213 | */ 214 | private HandlerThread mBackgroundThread; 215 | 216 | /** 217 | * A {@link Handler} for running tasks in the background. 218 | */ 219 | private Handler mBackgroundHandler; 220 | 221 | /** 222 | * An {@link ImageReader} that handles still image capture. 223 | */ 224 | private ImageReader mImageReader; 225 | 226 | /** 227 | * This is the output file for our picture. 228 | */ 229 | private File mFile; 230 | 231 | /** 232 | * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a 233 | * still image is ready to be saved. 234 | */ 235 | private final ImageReader.OnImageAvailableListener mOnImageAvailableListener 236 | = new ImageReader.OnImageAvailableListener() { 237 | 238 | @Override 239 | public void onImageAvailable(ImageReader reader) { 240 | mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile)); 241 | } 242 | 243 | }; 244 | 245 | /** 246 | * {@link CaptureRequest.Builder} for the camera preview 247 | */ 248 | private CaptureRequest.Builder mPreviewRequestBuilder; 249 | 250 | /** 251 | * {@link CaptureRequest} generated by {@link #mPreviewRequestBuilder} 252 | */ 253 | private CaptureRequest mPreviewRequest; 254 | 255 | /** 256 | * The current state of camera state for taking pictures. 257 | * 258 | * @see #mCaptureCallback 259 | */ 260 | private int mState = STATE_PREVIEW; 261 | 262 | /** 263 | * A {@link Semaphore} to prevent the app from exiting before closing the camera. 264 | */ 265 | private final Semaphore mCameraOpenCloseLock = new Semaphore(1); 266 | 267 | /** 268 | * A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture. 269 | */ 270 | private final CameraCaptureSession.CaptureCallback mCaptureCallback 271 | = new CameraCaptureSession.CaptureCallback() { 272 | 273 | private void process(CaptureResult result) { 274 | switch (mState) { 275 | case STATE_PREVIEW: { 276 | // We have nothing to do when the camera preview is working normally. 277 | break; 278 | } 279 | case STATE_WAITING_LOCK: { 280 | Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); 281 | if (afState == null) { 282 | captureStillPicture(); 283 | } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState || 284 | CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) { 285 | // CONTROL_AE_STATE can be null on some devices 286 | Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); 287 | if (aeState == null || 288 | aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { 289 | mState = STATE_PICTURE_TAKEN; 290 | captureStillPicture(); 291 | } else { 292 | runPrecaptureSequence(); 293 | } 294 | } 295 | break; 296 | } 297 | case STATE_WAITING_PRECAPTURE: { 298 | // CONTROL_AE_STATE can be null on some devices 299 | Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); 300 | if (aeState == null || 301 | aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || 302 | aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) { 303 | mState = STATE_WAITING_NON_PRECAPTURE; 304 | } 305 | break; 306 | } 307 | case STATE_WAITING_NON_PRECAPTURE: { 308 | // CONTROL_AE_STATE can be null on some devices 309 | Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); 310 | if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { 311 | mState = STATE_PICTURE_TAKEN; 312 | captureStillPicture(); 313 | } 314 | break; 315 | } 316 | default: 317 | break; 318 | } 319 | } 320 | 321 | @Override 322 | public void onCaptureProgressed(@NonNull CameraCaptureSession session, 323 | @NonNull CaptureRequest request, 324 | @NonNull CaptureResult partialResult) { 325 | process(partialResult); 326 | } 327 | 328 | @Override 329 | public void onCaptureCompleted(@NonNull CameraCaptureSession session, 330 | @NonNull CaptureRequest request, 331 | @NonNull TotalCaptureResult result) { 332 | process(result); 333 | } 334 | 335 | }; 336 | 337 | /** 338 | * Shows a {@link Toast} on the UI thread. 339 | * 340 | * @param text The message to show 341 | */ 342 | private void showToast(final String text) { 343 | final Activity activity = getActivity(); 344 | if (activity != null) { 345 | activity.runOnUiThread(new Runnable() { 346 | @Override 347 | public void run() { 348 | Toast.makeText(activity, text, Toast.LENGTH_SHORT).show(); 349 | } 350 | }); 351 | } 352 | } 353 | 354 | /** 355 | * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose 356 | * width and height are at least as large as the respective requested values, and whose aspect 357 | * ratio matches with the specified value. 358 | * 359 | * @param choices The list of sizes that the camera supports for the intended output class 360 | * @param width The minimum desired width 361 | * @param height The minimum desired height 362 | * @param aspectRatio The aspect ratio 363 | * @return The optimal {@code Size}, or an arbitrary one if none were big enough 364 | */ 365 | private static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) { 366 | // Collect the supported resolutions that are at least as big as the preview Surface 367 | List bigEnough = new ArrayList<>(); 368 | int w = aspectRatio.getWidth(); 369 | int h = aspectRatio.getHeight(); 370 | for (Size option : choices) { 371 | if (option.getHeight() == option.getWidth() * h / w && 372 | option.getWidth() >= width && option.getHeight() >= height) { 373 | bigEnough.add(option); 374 | } 375 | } 376 | 377 | // Pick the smallest of those, assuming we found any 378 | if (bigEnough.size() > 0) { 379 | return Collections.min(bigEnough, new CompareSizesByArea()); 380 | } else { 381 | Log.e(TAG, "Couldn't find any suitable preview size"); 382 | return choices[0]; 383 | } 384 | } 385 | 386 | public static Camera2BasicFragment newInstance() { 387 | return new Camera2BasicFragment(); 388 | } 389 | 390 | @Override 391 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 392 | Bundle savedInstanceState) { 393 | return inflater.inflate(R.layout.fragment_camera2_basic, container, false); 394 | } 395 | 396 | @Override 397 | public void onViewCreated(final View view, Bundle savedInstanceState) { 398 | view.findViewById(R.id.picture_scene).setOnClickListener(this); 399 | view.findViewById(R.id.picture_detect).setOnClickListener(this); 400 | view.findViewById(R.id.info).setOnClickListener(this); 401 | mTextureView = (AutoFitTextureView) view.findViewById(R.id.texture); 402 | } 403 | 404 | @Override 405 | public void onActivityCreated(Bundle savedInstanceState) { 406 | super.onActivityCreated(savedInstanceState); 407 | mFile = new File(getActivity().getExternalFilesDir(null), "pic.jpg"); 408 | } 409 | 410 | @Override 411 | public void onResume() { 412 | super.onResume(); 413 | startBackgroundThread(); 414 | 415 | // When the screen is turned off and turned back on, the SurfaceTexture is already 416 | // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open 417 | // a camera and start preview from here (otherwise, we wait until the surface is ready in 418 | // the SurfaceTextureListener). 419 | if (mTextureView.isAvailable()) { 420 | openCamera(mTextureView.getWidth(), mTextureView.getHeight()); 421 | } else { 422 | mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); 423 | } 424 | } 425 | 426 | @Override 427 | public void onPause() { 428 | closeCamera(); 429 | stopBackgroundThread(); 430 | super.onPause(); 431 | } 432 | 433 | private void requestCameraPermission() { 434 | if (FragmentCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) { 435 | new ConfirmationDialog().show(getChildFragmentManager(), FRAGMENT_DIALOG); 436 | } else { 437 | FragmentCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 438 | REQUEST_CAMERA_PERMISSION); 439 | } 440 | } 441 | 442 | @Override 443 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 444 | @NonNull int[] grantResults) { 445 | if (requestCode == REQUEST_CAMERA_PERMISSION) { 446 | if (grantResults.length != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) { 447 | ErrorDialog.newInstance(getString(R.string.request_permission)) 448 | .show(getChildFragmentManager(), FRAGMENT_DIALOG); 449 | } 450 | } else { 451 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 452 | } 453 | } 454 | 455 | /** 456 | * Sets up member variables related to camera. 457 | * 458 | * @param width The width of available size for camera preview 459 | * @param height The height of available size for camera preview 460 | */ 461 | private void setUpCameraOutputs(int width, int height) { 462 | Activity activity = getActivity(); 463 | CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); 464 | try { 465 | for (String cameraId : manager.getCameraIdList()) { 466 | CameraCharacteristics characteristics 467 | = manager.getCameraCharacteristics(cameraId); 468 | 469 | // We don't use a front facing camera in this sample. 470 | Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); 471 | if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) { 472 | continue; 473 | } 474 | 475 | StreamConfigurationMap map = characteristics.get( 476 | CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 477 | if (map == null) { 478 | continue; 479 | } 480 | 481 | // For still image captures, we use the largest available size. 482 | Size largest = Collections.max( 483 | Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), 484 | new CompareSizesByArea()); 485 | mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), 486 | ImageFormat.JPEG, /*maxImages*/2); 487 | mImageReader.setOnImageAvailableListener( 488 | mOnImageAvailableListener, mBackgroundHandler); 489 | 490 | // Danger, W.R.! Attempting to use too large a preview size could exceed the camera 491 | // bus' bandwidth limitation, resulting in gorgeous previews but the storage of 492 | // garbage capture data. 493 | mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), 494 | width, height, largest); 495 | 496 | // We fit the aspect ratio of TextureView to the size of preview we picked. 497 | int orientation = getResources().getConfiguration().orientation; 498 | if (orientation == Configuration.ORIENTATION_LANDSCAPE) { 499 | mTextureView.setAspectRatio( 500 | mPreviewSize.getWidth(), mPreviewSize.getHeight()); 501 | } else { 502 | mTextureView.setAspectRatio( 503 | mPreviewSize.getHeight(), mPreviewSize.getWidth()); 504 | } 505 | 506 | mCameraId = cameraId; 507 | return; 508 | } 509 | } catch (CameraAccessException e) { 510 | e.printStackTrace(); 511 | } catch (NullPointerException e) { 512 | // Currently an NPE is thrown when the Camera2API is used but not supported on the 513 | // device this code runs. 514 | ErrorDialog.newInstance(getString(R.string.camera_error)) 515 | .show(getChildFragmentManager(), FRAGMENT_DIALOG); 516 | } 517 | } 518 | 519 | /** 520 | * Opens the camera specified by {@link Camera2BasicFragment#mCameraId}. 521 | */ 522 | private void openCamera(int width, int height) { 523 | setUpCameraOutputs(width, height); 524 | configureTransform(width, height); 525 | Activity activity = getActivity(); 526 | CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); 527 | try { 528 | if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { 529 | throw new RuntimeException("Time out waiting to lock camera opening."); 530 | } 531 | manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler); 532 | } catch (CameraAccessException e) { 533 | e.printStackTrace(); 534 | } catch (InterruptedException e) { 535 | throw new RuntimeException("Interrupted while trying to lock camera opening.", e); 536 | } 537 | } 538 | 539 | /** 540 | * Closes the current {@link CameraDevice}. 541 | */ 542 | private void closeCamera() { 543 | try { 544 | mCameraOpenCloseLock.acquire(); 545 | if (null != mCaptureSession) { 546 | mCaptureSession.close(); 547 | mCaptureSession = null; 548 | } 549 | if (null != mCameraDevice) { 550 | mCameraDevice.close(); 551 | mCameraDevice = null; 552 | } 553 | if (null != mImageReader) { 554 | mImageReader.close(); 555 | mImageReader = null; 556 | } 557 | } catch (InterruptedException e) { 558 | throw new RuntimeException("Interrupted while trying to lock camera closing.", e); 559 | } finally { 560 | mCameraOpenCloseLock.release(); 561 | } 562 | } 563 | 564 | /** 565 | * Starts a background thread and its {@link Handler}. 566 | */ 567 | private void startBackgroundThread() { 568 | mBackgroundThread = new HandlerThread("CameraBackground"); 569 | mBackgroundThread.start(); 570 | mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); 571 | } 572 | 573 | /** 574 | * Stops the background thread and its {@link Handler}. 575 | */ 576 | private void stopBackgroundThread() { 577 | mBackgroundThread.quitSafely(); 578 | try { 579 | mBackgroundThread.join(); 580 | mBackgroundThread = null; 581 | mBackgroundHandler = null; 582 | } catch (InterruptedException e) { 583 | e.printStackTrace(); 584 | } 585 | } 586 | 587 | /** 588 | * Creates a new {@link CameraCaptureSession} for camera preview. 589 | */ 590 | private void createCameraPreviewSession() { 591 | try { 592 | SurfaceTexture texture = mTextureView.getSurfaceTexture(); 593 | assert texture != null; 594 | 595 | // We configure the size of default buffer to be the size of camera preview we want. 596 | texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); 597 | 598 | // This is the output Surface we need to start preview. 599 | Surface surface = new Surface(texture); 600 | 601 | // We set up a CaptureRequest.Builder with the output Surface. 602 | mPreviewRequestBuilder 603 | = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 604 | mPreviewRequestBuilder.addTarget(surface); 605 | 606 | // Here, we create a CameraCaptureSession for camera preview. 607 | mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), 608 | new CameraCaptureSession.StateCallback() { 609 | 610 | @Override 611 | public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { 612 | // The camera is already closed 613 | if (null == mCameraDevice) { 614 | return; 615 | } 616 | 617 | // When the session is ready, we start displaying the preview. 618 | mCaptureSession = cameraCaptureSession; 619 | try { 620 | // Auto focus should be continuous for camera preview. 621 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, 622 | CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); 623 | // Flash is automatically enabled when necessary. 624 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, 625 | CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); 626 | 627 | // Finally, we start displaying the camera preview. 628 | mPreviewRequest = mPreviewRequestBuilder.build(); 629 | mCaptureSession.setRepeatingRequest(mPreviewRequest, 630 | mCaptureCallback, mBackgroundHandler); 631 | } catch (CameraAccessException e) { 632 | e.printStackTrace(); 633 | } 634 | } 635 | 636 | @Override 637 | public void onConfigureFailed( 638 | @NonNull CameraCaptureSession cameraCaptureSession) { 639 | showToast("Failed"); 640 | } 641 | }, null 642 | ); 643 | } catch (CameraAccessException e) { 644 | e.printStackTrace(); 645 | } 646 | } 647 | 648 | /** 649 | * Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`. 650 | * This method should be called after the camera preview size is determined in 651 | * setUpCameraOutputs and also the size of `mTextureView` is fixed. 652 | * 653 | * @param viewWidth The width of `mTextureView` 654 | * @param viewHeight The height of `mTextureView` 655 | */ 656 | private void configureTransform(int viewWidth, int viewHeight) { 657 | Activity activity = getActivity(); 658 | if (null == mTextureView || null == mPreviewSize || null == activity) { 659 | return; 660 | } 661 | int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); 662 | Matrix matrix = new Matrix(); 663 | RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); 664 | RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth()); 665 | float centerX = viewRect.centerX(); 666 | float centerY = viewRect.centerY(); 667 | if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { 668 | bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); 669 | matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); 670 | float scale = Math.max( 671 | (float) viewHeight / mPreviewSize.getHeight(), 672 | (float) viewWidth / mPreviewSize.getWidth()); 673 | matrix.postScale(scale, scale, centerX, centerY); 674 | matrix.postRotate(90 * (rotation - 2), centerX, centerY); 675 | } else if (Surface.ROTATION_180 == rotation) { 676 | matrix.postRotate(180, centerX, centerY); 677 | } 678 | mTextureView.setTransform(matrix); 679 | } 680 | 681 | /** 682 | * Initiate a still image capture. 683 | */ 684 | private void sceneRecognize() { 685 | Log.d(TAG, "sceneRecognize"); 686 | mAction = VisionAction.SceneRecognition; 687 | lockFocus(); 688 | } 689 | 690 | private void objDetect() { 691 | Log.d(TAG, "objDetect"); 692 | mAction = VisionAction.ObjDetect; 693 | lockFocus(); 694 | } 695 | /** 696 | * Lock the focus as the first step for a still image capture. 697 | */ 698 | private void lockFocus() { 699 | try { 700 | // This is how to tell the camera to lock focus. 701 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 702 | CameraMetadata.CONTROL_AF_TRIGGER_START); 703 | // Tell #mCaptureCallback to wait for the lock. 704 | mState = STATE_WAITING_LOCK; 705 | mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, 706 | mBackgroundHandler); 707 | } catch (CameraAccessException e) { 708 | e.printStackTrace(); 709 | } 710 | } 711 | 712 | /** 713 | * Run the precapture sequence for capturing a still image. This method should be called when 714 | * we get a response in {@link #mCaptureCallback} from {@link #lockFocus()}. 715 | */ 716 | private void runPrecaptureSequence() { 717 | try { 718 | // This is how to tell the camera to trigger. 719 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, 720 | CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); 721 | // Tell #mCaptureCallback to wait for the precapture sequence to be set. 722 | mState = STATE_WAITING_PRECAPTURE; 723 | mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, 724 | mBackgroundHandler); 725 | } catch (CameraAccessException e) { 726 | e.printStackTrace(); 727 | } 728 | } 729 | 730 | /** 731 | * Capture a still picture. This method should be called when we get a response in 732 | * {@link #mCaptureCallback} from both {@link #lockFocus()}. 733 | */ 734 | private void captureStillPicture() { 735 | try { 736 | final Activity activity = getActivity(); 737 | if (null == activity || null == mCameraDevice) { 738 | return; 739 | } 740 | // This is the CaptureRequest.Builder that we use to take a picture. 741 | final CaptureRequest.Builder captureBuilder = 742 | mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); 743 | captureBuilder.addTarget(mImageReader.getSurface()); 744 | 745 | // Use the same AE and AF modes as the preview. 746 | captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, 747 | CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); 748 | captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, 749 | CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); 750 | 751 | // Orientation 752 | int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); 753 | captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation)); 754 | 755 | CameraCaptureSession.CaptureCallback captureCallback 756 | = new CameraCaptureSession.CaptureCallback() { 757 | 758 | @Override 759 | public void onCaptureCompleted(@NonNull CameraCaptureSession session, 760 | @NonNull CaptureRequest request, 761 | @NonNull TotalCaptureResult result) { 762 | Log.d(TAG, mFile.toString()); 763 | unlockFocus(); 764 | 765 | Intent intent = null; 766 | if (mAction == VisionAction.ObjDetect) { 767 | intent = new Intent(getActivity(), ObjectDetectActivity.class); 768 | } else if (mAction == VisionAction.SceneRecognition) { 769 | intent = new Intent(getActivity(), SceneRecognitionActivity.class); 770 | } 771 | if (intent != null) { 772 | Bundle bundle = new Bundle(); 773 | bundle.putString(Camera2BasicFragment.KEY_IMGPATH, mFile.toString()); 774 | intent.putExtras(bundle); 775 | startActivity(intent); 776 | } 777 | } 778 | }; 779 | 780 | mCaptureSession.stopRepeating(); 781 | mCaptureSession.capture(captureBuilder.build(), captureCallback, null); 782 | } catch (CameraAccessException e) { 783 | e.printStackTrace(); 784 | } 785 | } 786 | 787 | /** 788 | * Unlock the focus. This method should be called when still image capture sequence is 789 | * finished. 790 | */ 791 | private void unlockFocus() { 792 | try { 793 | // Reset the auto-focus trigger 794 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 795 | CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); 796 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, 797 | CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); 798 | mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, 799 | mBackgroundHandler); 800 | // After this, the camera will go back to the normal state of preview. 801 | mState = STATE_PREVIEW; 802 | mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, 803 | mBackgroundHandler); 804 | } catch (CameraAccessException e) { 805 | e.printStackTrace(); 806 | } 807 | } 808 | 809 | @Override 810 | public void onClick(View view) { 811 | switch (view.getId()) { 812 | case R.id.picture_scene: { 813 | sceneRecognize(); 814 | break; 815 | } 816 | case R.id.picture_detect: { 817 | objDetect(); 818 | break; 819 | } 820 | case R.id.info: { 821 | Activity activity = getActivity(); 822 | if (null != activity) { 823 | new AlertDialog.Builder(activity) 824 | .setMessage(R.string.intro_message) 825 | .setPositiveButton(android.R.string.ok, null) 826 | .show(); 827 | } 828 | break; 829 | } 830 | default: 831 | break; 832 | } 833 | } 834 | 835 | /** 836 | * Saves a JPEG {@link Image} into the specified {@link File}. 837 | */ 838 | private static class ImageSaver implements Runnable { 839 | 840 | /** 841 | * The JPEG image 842 | */ 843 | private final Image mImage; 844 | /** 845 | * The file we save the image into. 846 | */ 847 | private final File mFile; 848 | 849 | public ImageSaver(Image image, File file) { 850 | mImage = image; 851 | mFile = file; 852 | } 853 | 854 | @Override 855 | public void run() { 856 | ByteBuffer buffer = mImage.getPlanes()[0].getBuffer(); 857 | byte[] bytes = new byte[buffer.remaining()]; 858 | buffer.get(bytes); 859 | FileOutputStream output = null; 860 | try { 861 | output = new FileOutputStream(mFile); 862 | output.write(bytes); 863 | } catch (IOException e) { 864 | e.printStackTrace(); 865 | } finally { 866 | mImage.close(); 867 | if (null != output) { 868 | try { 869 | output.close(); 870 | } catch (IOException e) { 871 | e.printStackTrace(); 872 | } 873 | } 874 | } 875 | } 876 | 877 | } 878 | 879 | /** 880 | * Compares two {@code Size}s based on their areas. 881 | */ 882 | static class CompareSizesByArea implements Comparator { 883 | 884 | @Override 885 | public int compare(Size lhs, Size rhs) { 886 | // We cast here to ensure the multiplications won't overflow 887 | return Long.signum((long) lhs.getWidth() * lhs.getHeight() - 888 | (long) rhs.getWidth() * rhs.getHeight()); 889 | } 890 | 891 | } 892 | 893 | /** 894 | * Shows an error message dialog. 895 | */ 896 | public static class ErrorDialog extends DialogFragment { 897 | 898 | private static final String ARG_MESSAGE = "message"; 899 | 900 | public static ErrorDialog newInstance(String message) { 901 | ErrorDialog dialog = new ErrorDialog(); 902 | Bundle args = new Bundle(); 903 | args.putString(ARG_MESSAGE, message); 904 | dialog.setArguments(args); 905 | return dialog; 906 | } 907 | 908 | @Override 909 | public Dialog onCreateDialog(Bundle savedInstanceState) { 910 | final Activity activity = getActivity(); 911 | return new AlertDialog.Builder(activity) 912 | .setMessage(getArguments().getString(ARG_MESSAGE)) 913 | .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 914 | @Override 915 | public void onClick(DialogInterface dialogInterface, int i) { 916 | activity.finish(); 917 | } 918 | }) 919 | .create(); 920 | } 921 | 922 | } 923 | 924 | /** 925 | * Shows OK/Cancel confirmation dialog about camera permission. 926 | */ 927 | public static class ConfirmationDialog extends DialogFragment { 928 | 929 | @Override 930 | public Dialog onCreateDialog(Bundle savedInstanceState) { 931 | final Fragment parent = getParentFragment(); 932 | return new AlertDialog.Builder(getActivity()) 933 | .setMessage(R.string.request_permission) 934 | .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 935 | @Override 936 | public void onClick(DialogInterface dialog, int which) { 937 | FragmentCompat.requestPermissions(parent, 938 | new String[]{Manifest.permission.CAMERA}, 939 | REQUEST_CAMERA_PERMISSION); 940 | } 941 | }) 942 | .setNegativeButton(android.R.string.cancel, 943 | new DialogInterface.OnClickListener() { 944 | @Override 945 | public void onClick(DialogInterface dialog, int which) { 946 | Activity activity = parent.getActivity(); 947 | if (activity != null) { 948 | activity.finish(); 949 | } 950 | } 951 | }) 952 | .create(); 953 | } 954 | } 955 | 956 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tzutalin/vision/demo/CameraActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 TzuTaLin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.tzutalin.vision.demo; 17 | 18 | import android.Manifest; 19 | import android.app.Activity; 20 | import android.content.pm.PackageManager; 21 | import android.os.Build; 22 | import android.os.Bundle; 23 | import android.support.v4.app.ActivityCompat; 24 | import android.widget.Toast; 25 | 26 | import com.tzutalin.vision.visionrecognition.R; 27 | 28 | public class CameraActivity extends Activity { 29 | 30 | private static final int REQUEST_CODE_PERMISSION = 1; 31 | 32 | private static final String TAG = "CameraActivity"; 33 | 34 | // Storage Permissions 35 | private static String[] PERMISSIONS_REQ = { 36 | Manifest.permission.READ_EXTERNAL_STORAGE, 37 | Manifest.permission.WRITE_EXTERNAL_STORAGE, 38 | Manifest.permission.CAMERA 39 | }; 40 | 41 | @Override 42 | protected void onCreate(Bundle savedInstanceState) { 43 | super.onCreate(savedInstanceState); 44 | setContentView(R.layout.activity_camera); 45 | 46 | boolean avialbe_permission = true; 47 | // For API 23+ you need to request the read/write permissions even if they are already in your manifest. 48 | int currentapiVersion = android.os.Build.VERSION.SDK_INT; 49 | if (currentapiVersion >= Build.VERSION_CODES.M ) { 50 | avialbe_permission = verifyPermissions(this); 51 | } 52 | 53 | if (avialbe_permission && null == savedInstanceState) { 54 | getFragmentManager().beginTransaction() 55 | .replace(R.id.container, Camera2BasicFragment.newInstance()) 56 | .commit(); 57 | } 58 | } 59 | 60 | private static boolean verifyPermissions(Activity activity) { 61 | // Check if we have write permission 62 | int write_permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); 63 | int read_persmission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); 64 | int camera_permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.CAMERA); 65 | 66 | if (write_permission != PackageManager.PERMISSION_GRANTED || 67 | read_persmission != PackageManager.PERMISSION_GRANTED || 68 | camera_permission != PackageManager.PERMISSION_GRANTED) { 69 | // We don't have permission so prompt the user 70 | ActivityCompat.requestPermissions( 71 | activity, 72 | PERMISSIONS_REQ, 73 | REQUEST_CODE_PERMISSION 74 | ); 75 | return false; 76 | } else { 77 | return true; 78 | } 79 | } 80 | 81 | public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { 82 | try { 83 | // Restart it after granting permission 84 | if (requestCode == REQUEST_CODE_PERMISSION) { 85 | finish(); 86 | startActivity(getIntent()); 87 | } 88 | } catch (Exception e) { 89 | Toast.makeText(this, "Something went wrong", Toast.LENGTH_LONG).show(); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/com/tzutalin/vision/demo/ObjectDetectActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 TzuTaLin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.tzutalin.vision.demo; 17 | 18 | import android.app.Activity; 19 | import android.app.ProgressDialog; 20 | import android.graphics.Bitmap; 21 | import android.graphics.BitmapFactory; 22 | import android.graphics.drawable.BitmapDrawable; 23 | import android.graphics.drawable.Drawable; 24 | import android.os.AsyncTask; 25 | import android.os.Bundle; 26 | import android.util.Log; 27 | import android.view.Menu; 28 | import android.view.MenuItem; 29 | import android.view.Window; 30 | import android.widget.Toast; 31 | 32 | import com.dexafree.materialList.card.Card; 33 | import com.dexafree.materialList.card.provider.BigImageCardProvider; 34 | import com.dexafree.materialList.view.MaterialListView; 35 | import com.tzutalin.vision.visionrecognition.ObjectDetector; 36 | import com.tzutalin.vision.visionrecognition.R; 37 | import com.tzutalin.vision.visionrecognition.VisionClassifierCreator; 38 | import com.tzutalin.vision.visionrecognition.VisionDetRet; 39 | 40 | import java.io.File; 41 | import java.util.ArrayList; 42 | import java.util.List; 43 | 44 | public class ObjectDetectActivity extends Activity { 45 | private final static String TAG = "ObjectDetectActivity"; 46 | private ObjectDetector mObjectDet; 47 | // UI 48 | MaterialListView mListView; 49 | @Override 50 | protected void onCreate(Bundle savedInstanceState) { 51 | super.onCreate(savedInstanceState); 52 | requestWindowFeature(Window.FEATURE_NO_TITLE); 53 | setContentView(R.layout.activity_object_detect); 54 | mListView = (MaterialListView) findViewById(R.id.material_listview); 55 | final String key = Camera2BasicFragment.KEY_IMGPATH; 56 | String imgPath = getIntent().getExtras().getString(key); 57 | if (!new File(imgPath).exists()) { 58 | Toast.makeText(this, "No file path", Toast.LENGTH_SHORT).show(); 59 | this.finish(); 60 | return; 61 | } 62 | DetectTask task = new DetectTask(); 63 | task.execute(imgPath); 64 | } 65 | 66 | @Override 67 | public boolean onCreateOptionsMenu(Menu menu) { 68 | // Inflate the menu; this adds items to the action bar if it is present. 69 | getMenuInflater().inflate(R.menu.menu_object_detect, menu); 70 | return true; 71 | } 72 | 73 | @Override 74 | public boolean onOptionsItemSelected(MenuItem item) { 75 | int id = item.getItemId(); 76 | if (id == R.id.action_settings) { 77 | return true; 78 | } 79 | 80 | return super.onOptionsItemSelected(item); 81 | } 82 | 83 | // ========================================================== 84 | // Tasks inner class 85 | // ========================================================== 86 | private class DetectTask extends AsyncTask> { 87 | private ProgressDialog mmDialog; 88 | 89 | @Override 90 | protected void onPreExecute() { 91 | super.onPreExecute(); 92 | mmDialog = ProgressDialog.show(ObjectDetectActivity.this, getString(R.string.dialog_wait),getString(R.string.dialog_object_decscription), true); 93 | } 94 | 95 | @Override 96 | protected List doInBackground(String... strings) { 97 | final String filePath = strings[0]; 98 | long startTime; 99 | long endTime; 100 | Log.d(TAG, "DetectTask filePath:" + filePath); 101 | if (mObjectDet == null) { 102 | try { 103 | mObjectDet = VisionClassifierCreator.createObjectDetector(getApplicationContext()); 104 | // TODO: Get Image's height and width 105 | mObjectDet.init(0, 0); 106 | } catch (IllegalAccessException e) { 107 | e.printStackTrace(); 108 | } 109 | } 110 | List ret = new ArrayList<>(); 111 | if (mObjectDet != null) { 112 | startTime = System.currentTimeMillis(); 113 | Log.d(TAG, "Start objDetect"); 114 | ret.addAll(mObjectDet.classifyByPath(filePath)); 115 | Log.d(TAG, "end objDetect"); 116 | endTime = System.currentTimeMillis(); 117 | final double diffTime = (double) (endTime - startTime) / 1000; 118 | runOnUiThread(new Runnable() { 119 | @Override 120 | public void run() { 121 | Toast.makeText(ObjectDetectActivity.this, "Take " + diffTime + " second", Toast.LENGTH_LONG).show(); 122 | } 123 | }); 124 | } 125 | File beDeletedFile = new File(filePath); 126 | if (beDeletedFile.exists()) { 127 | beDeletedFile.delete(); 128 | } else { 129 | Log.d(TAG, "file does not exist " + filePath); 130 | } 131 | 132 | mObjectDet.deInit(); 133 | return ret; 134 | } 135 | 136 | @Override 137 | protected void onPostExecute(List rets) { 138 | super.onPostExecute(rets); 139 | if (mmDialog != null) { 140 | mmDialog.dismiss(); 141 | } 142 | // TODO: Remvoe it 143 | BitmapFactory.Options options = new BitmapFactory.Options(); 144 | options.inSampleSize = 4; 145 | String retImgPath = "/sdcard/temp.jpg"; 146 | Bitmap bitmap = BitmapFactory.decodeFile(retImgPath, options); 147 | 148 | Drawable d = new BitmapDrawable(getResources(), bitmap); 149 | Card card = new Card.Builder(ObjectDetectActivity.this) 150 | .withProvider(BigImageCardProvider.class) 151 | .setDrawable(d) 152 | .endConfig() 153 | .build(); 154 | mListView.add(card); 155 | for (VisionDetRet item : rets) { 156 | StringBuilder sb = new StringBuilder(); 157 | sb.append(item.getLabel()) 158 | .append(", Prob:").append(item.getConfidence()) 159 | .append(" [") 160 | .append(item.getLeft()).append(',') 161 | .append(item.getTop()).append(',') 162 | .append(item.getRight()).append(',') 163 | .append(item.getBottom()) 164 | .append(']'); 165 | Log.d(TAG, sb.toString()); 166 | 167 | if (!item.getLabel().equalsIgnoreCase("background")) { 168 | card = new Card.Builder(ObjectDetectActivity.this) 169 | .withProvider(BigImageCardProvider.class) 170 | .setTitle("Detect Result") 171 | .setDescription(sb.toString()) 172 | .endConfig() 173 | .build(); 174 | mListView.add(card); 175 | } 176 | } 177 | 178 | File beDeletedFile = new File(retImgPath); 179 | if (beDeletedFile.exists()) { 180 | beDeletedFile.delete(); 181 | } 182 | 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /app/src/main/java/com/tzutalin/vision/demo/SceneRecognitionActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 TzuTaLin 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 com.tzutalin.vision.demo; 18 | 19 | import android.app.Activity; 20 | import android.app.ProgressDialog; 21 | import android.graphics.Bitmap; 22 | import android.graphics.BitmapFactory; 23 | import android.graphics.drawable.BitmapDrawable; 24 | import android.graphics.drawable.Drawable; 25 | import android.os.AsyncTask; 26 | import android.os.Bundle; 27 | import android.util.Log; 28 | import android.view.Menu; 29 | import android.view.MenuItem; 30 | import android.view.Window; 31 | import android.widget.Toast; 32 | 33 | import com.tzutalin.vision.visionrecognition.R; 34 | import com.tzutalin.vision.visionrecognition.SceneClassifier; 35 | import com.tzutalin.vision.visionrecognition.VisionClassifierCreator; 36 | import com.tzutalin.vision.visionrecognition.VisionDetRet; 37 | import com.dexafree.materialList.card.Card; 38 | import com.dexafree.materialList.card.provider.BigImageCardProvider; 39 | import com.dexafree.materialList.view.MaterialListView; 40 | 41 | import java.io.File; 42 | import java.util.ArrayList; 43 | import java.util.List; 44 | 45 | public class SceneRecognitionActivity extends Activity { 46 | private final static String TAG = "SceneRecognitionActivity"; 47 | // UI 48 | MaterialListView mListView; 49 | private SceneClassifier mClassifier; 50 | 51 | @Override 52 | protected void onCreate(Bundle savedInstanceState) { 53 | super.onCreate(savedInstanceState); 54 | requestWindowFeature(Window.FEATURE_NO_TITLE); 55 | setContentView(R.layout.activity_scene_recognition); 56 | mListView = (MaterialListView) findViewById(R.id.material_listview); 57 | final String key = Camera2BasicFragment.KEY_IMGPATH; 58 | String imgPath = getIntent().getExtras().getString(key); 59 | if (!new File(imgPath).exists()) { 60 | Toast.makeText(this, "No file path", Toast.LENGTH_SHORT).show(); 61 | this.finish(); 62 | return; 63 | } 64 | 65 | PredictTask task = new PredictTask(); 66 | task.execute(imgPath); 67 | 68 | BitmapFactory.Options options = new BitmapFactory.Options(); 69 | options.inSampleSize = 4; 70 | Bitmap bm = BitmapFactory.decodeFile(imgPath, options); 71 | Drawable d = new BitmapDrawable(getResources(), bm); 72 | 73 | Card card = new Card.Builder(SceneRecognitionActivity.this) 74 | .withProvider(BigImageCardProvider.class) 75 | .setDescription("Input image") 76 | .setDrawable(d) 77 | .endConfig() 78 | .build(); 79 | mListView.add(card); 80 | } 81 | 82 | @Override 83 | protected void onDestroy() { 84 | super.onDestroy(); 85 | if (mClassifier != null) { 86 | mClassifier.deInit(); 87 | } 88 | } 89 | 90 | @Override 91 | public boolean onCreateOptionsMenu(Menu menu) { 92 | // Inflate the menu; this adds items to the action bar if it is present. 93 | getMenuInflater().inflate(R.menu.menu_scene_recognition, menu); 94 | return true; 95 | } 96 | 97 | @Override 98 | public boolean onOptionsItemSelected(MenuItem item) { 99 | // Handle action bar item clicks here. The action bar will 100 | // automatically handle clicks on the Home/Up button, so long 101 | // as you specify a parent activity in AndroidManifest.xml. 102 | int id = item.getItemId(); 103 | 104 | //noinspection SimplifiableIfStatement 105 | if (id == R.id.action_settings) { 106 | return true; 107 | } 108 | 109 | return super.onOptionsItemSelected(item); 110 | } 111 | 112 | // ========================================================== 113 | // Tasks inner class 114 | // ========================================================== 115 | private class PredictTask extends AsyncTask> { 116 | private ProgressDialog mmDialog; 117 | 118 | @Override 119 | protected void onPreExecute() { 120 | super.onPreExecute(); 121 | mmDialog = ProgressDialog.show(SceneRecognitionActivity.this, getString(R.string.dialog_wait),getString(R.string.dialog_scene_decscription), true); 122 | } 123 | 124 | @Override 125 | protected List doInBackground(String... strings) { 126 | initCaffeMobile(); 127 | long startTime; 128 | long endTime; 129 | final String filePath = strings[0]; 130 | List rets = new ArrayList<>(); 131 | Log.d(TAG, "PredictTask filePath:" + filePath); 132 | if (mClassifier != null) { 133 | 134 | BitmapFactory.Options options = new BitmapFactory.Options(); 135 | options.inSampleSize = 4; 136 | Log.d(TAG, "format:" + options.inPreferredConfig); 137 | Bitmap bitmapImg = BitmapFactory.decodeFile(filePath, options); 138 | startTime = System.currentTimeMillis(); 139 | rets.addAll(mClassifier.classify(bitmapImg)); 140 | 141 | endTime = System.currentTimeMillis(); 142 | final double diffTime = (double) (endTime - startTime) / 1000; 143 | runOnUiThread(new Runnable() { 144 | @Override 145 | public void run() { 146 | Toast.makeText(SceneRecognitionActivity.this, "Take " + diffTime + " second", Toast.LENGTH_LONG).show(); 147 | } 148 | }); 149 | } 150 | File beDeletedFile = new File(filePath); 151 | if (beDeletedFile.exists()) { 152 | beDeletedFile.delete(); 153 | } else { 154 | Log.d(TAG, "file does not exist " + filePath); 155 | } 156 | return rets; 157 | } 158 | 159 | @Override 160 | protected void onPostExecute(List rets) { 161 | super.onPostExecute(rets); 162 | if (mmDialog != null) { 163 | mmDialog.dismiss(); 164 | } 165 | 166 | ArrayList items = new ArrayList<>(); 167 | for (VisionDetRet each : rets) { 168 | items.add("[" + each.getLabel() + "] Prob: " + each.getConfidence()); 169 | } 170 | 171 | int count = 0; 172 | for (String item : items) { 173 | count++; 174 | Card card = new Card.Builder(SceneRecognitionActivity.this) 175 | .withProvider(BigImageCardProvider.class) 176 | .setTitle("Top " + count) 177 | .setDescription(item) 178 | .endConfig() 179 | .build(); 180 | mListView.add(card); 181 | } 182 | } 183 | } 184 | // ========================================================== 185 | // Private methods 186 | // ========================================================== 187 | private void initCaffeMobile() { 188 | if (mClassifier == null) { 189 | try { 190 | mClassifier = VisionClassifierCreator.createSceneClassifier(getApplicationContext()); 191 | Log.d(TAG, "Start Load model"); 192 | // TODO : Fix it 193 | mClassifier.init(224,224); // init once 194 | Log.d(TAG, "End Load model"); 195 | } catch (IllegalAccessException e) { 196 | e.printStackTrace(); 197 | } 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_action_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzutalin/Android-Object-Detection/ff0319803e382cb9b7037bc49e38d7e33369d568/app/src/main/res/drawable-hdpi/ic_action_info.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzutalin/Android-Object-Detection/ff0319803e382cb9b7037bc49e38d7e33369d568/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/tile.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzutalin/Android-Object-Detection/ff0319803e382cb9b7037bc49e38d7e33369d568/app/src/main/res/drawable-hdpi/tile.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_action_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzutalin/Android-Object-Detection/ff0319803e382cb9b7037bc49e38d7e33369d568/app/src/main/res/drawable-mdpi/ic_action_info.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzutalin/Android-Object-Detection/ff0319803e382cb9b7037bc49e38d7e33369d568/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_action_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzutalin/Android-Object-Detection/ff0319803e382cb9b7037bc49e38d7e33369d568/app/src/main/res/drawable-xhdpi/ic_action_info.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzutalin/Android-Object-Detection/ff0319803e382cb9b7037bc49e38d7e33369d568/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_action_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzutalin/Android-Object-Detection/ff0319803e382cb9b7037bc49e38d7e33369d568/app/src/main/res/drawable-xxhdpi/ic_action_info.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzutalin/Android-Object-Detection/ff0319803e382cb9b7037bc49e38d7e33369d568/app/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzutalin/Android-Object-Detection/ff0319803e382cb9b7037bc49e38d7e33369d568/app/src/main/res/drawable/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/layout-land/fragment_camera2_basic.xml: -------------------------------------------------------------------------------- 1 | 16 | 19 | 20 | 27 | 28 | 38 | 39 |