diff --git a/.github/workflows/build-object-detection-final-app.yml b/.github/workflows/build-object-detection-final-app.yml new file mode 100644 index 0000000..81f0d51 --- /dev/null +++ b/.github/workflows/build-object-detection-final-app.yml @@ -0,0 +1,33 @@ +# Workflow name +name: Build CodelabMlkitAndroidObjectDetectionFinalApp + +on: + workflow_dispatch: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set Up JDK + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '11' + cache: 'gradle' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Make gradlew executable + run: chmod +x ./gradlew + working-directory: ./object-detection/final + + - name: Build CodelabMlkitAndroidObjectDetectionFinalApp app + working-directory: ./object-detection/final + run: ./gradlew :app:assembleDebug diff --git a/.github/workflows/build-object-detection-starter-app.yml b/.github/workflows/build-object-detection-starter-app.yml new file mode 100644 index 0000000..41e82ca --- /dev/null +++ b/.github/workflows/build-object-detection-starter-app.yml @@ -0,0 +1,33 @@ +# Workflow name +name: Build CodelabMlkitAndroidObjectDetectionStarterApp + +on: + workflow_dispatch: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set Up JDK + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '11' + cache: 'gradle' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Make gradlew executable + run: chmod +x ./gradlew + working-directory: ./object-detection/starter + + - name: Build CodelabMlkitAndroidObjectDetectionStarterApp app + working-directory: ./object-detection/starter + run: ./gradlew :app:assembleDebug diff --git a/.github/workflows/build-translate-starter-app.yml b/.github/workflows/build-translate-starter-app.yml new file mode 100644 index 0000000..6036d27 --- /dev/null +++ b/.github/workflows/build-translate-starter-app.yml @@ -0,0 +1,33 @@ +# Workflow name +name: Build CodelabMlkitAndroidTranslateStarterApp + +on: + workflow_dispatch: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set Up JDK + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '11' + cache: 'gradle' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Make gradlew executable + run: chmod +x ./gradlew + working-directory: ./translate/starter + + - name: Build CodelabMlkitAndroidTranslateStarterApp app + working-directory: ./translate/starter + run: ./gradlew :app:assembleDebug diff --git a/.github/workflows/build-vision-final-app.yml b/.github/workflows/build-vision-final-app.yml new file mode 100644 index 0000000..3879464 --- /dev/null +++ b/.github/workflows/build-vision-final-app.yml @@ -0,0 +1,33 @@ +# Workflow name +name: Build CodelabMlkitAndroidVisionFinalApp + +on: + workflow_dispatch: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set Up JDK + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '11' + cache: 'gradle' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Make gradlew executable + run: chmod +x ./gradlew + working-directory: ./vision/final + + - name: Build CodelabMlkitAndroidVisionFinalApp app + working-directory: ./vision/final + run: ./gradlew :app:assembleDebug diff --git a/.github/workflows/build-vision-starter-app.yml b/.github/workflows/build-vision-starter-app.yml new file mode 100644 index 0000000..566d95b --- /dev/null +++ b/.github/workflows/build-vision-starter-app.yml @@ -0,0 +1,33 @@ +# Workflow name +name: Build CodelabMlkitAndroidVisionStarterApp + +on: + workflow_dispatch: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set Up JDK + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '11' + cache: 'gradle' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Make gradlew executable + run: chmod +x ./gradlew + working-directory: ./vision/starter + + - name: Build CodelabMlkitAndroidVisionStarterApp app + working-directory: ./vision/starter + run: ./gradlew :app:assembleDebug diff --git a/.gitignore b/.gitignore index adf139f..6402252 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ build/ *.aar *.zip google-services.json +*.tflite .project .settings diff --git a/README.md b/README.md index 46eea6f..432fab4 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,13 @@ -Codelabs for ML Kit for Firebase +Codelabs for ML Kit ============ -This repository contains the code for the ML Kit for Firebase codelab: -* [Recognize text, facial features, and objects in images with ML Kit for Firebase](https://g.co/codelabs/mlkit-android) +This repository contains the code for the ML Kit codelabs: +* [Recognize text and facial features with ML Kit](https://g.co/codelabs/mlkit-android) Introduction ------------ In these codelabs, you will build an Android app that uses various features -of ML Kit for Firebase to recognize text, detect facial features, and identify -objects in images. You will learn how to use both the built in on-device and -cloud Text Recognition APIs, the face contour API, and how to use your own -Tensor Flow Lite custom models with ML Kit. +of ML Kit to recognize text and detect facial features. You will learn how to use the built in on-device Text Recognition API and the face contour API. Pre-requisites -------------- @@ -27,10 +24,10 @@ Screenshots Support ------- -- Stack Overflow: http://stackoverflow.com/questions/tagged/firebase-mlkit +- Stack Overflow: http://stackoverflow.com/questions/tagged/google-mlkit If you've found an error in this sample, please file an issue: -https://github.com/googlecodelabs/ml-kit/issues +https://github.com/googlecodelabs/mlkit-android/issues Patches are encouraged, and may be submitted by forking this project and submitting a pull request through GitHub. diff --git a/custom-model/final/.gitignore b/custom-model/final/.gitignore new file mode 100644 index 0000000..603b140 --- /dev/null +++ b/custom-model/final/.gitignore @@ -0,0 +1,14 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx diff --git a/starter/app/.gitignore b/custom-model/final/app/.gitignore old mode 100755 new mode 100644 similarity index 100% rename from starter/app/.gitignore rename to custom-model/final/app/.gitignore diff --git a/custom-model/final/app/build.gradle b/custom-model/final/app/build.gradle new file mode 100644 index 0000000..ca1e906 --- /dev/null +++ b/custom-model/final/app/build.gradle @@ -0,0 +1,53 @@ +apply plugin: 'com.android.application' + +apply plugin: 'kotlin-android' + +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.2" + defaultConfig { + applicationId "com.google.firebase.codelab.mlkit_custommodel" + minSdkVersion 21 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + aaptOptions { + noCompress "tflite" + } +} + +dependencies { + // Kotlin + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + + // Coroutines + def coroutines_version = '1.3.0' + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" + + // AndroidX and widgets + implementation 'androidx.core:core-ktx:1.1.0' + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-rc02' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + + // Firebase and MLKit + implementation 'com.google.firebase:firebase-core:17.2.1' + implementation 'com.google.firebase:firebase-ml-model-interpreter:22.0.0' + + // Test-only dependencies + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' +} +apply plugin: 'com.google.gms.google-services' \ No newline at end of file diff --git a/final/app/proguard-rules.pro b/custom-model/final/app/proguard-rules.pro old mode 100755 new mode 100644 similarity index 100% rename from final/app/proguard-rules.pro rename to custom-model/final/app/proguard-rules.pro diff --git a/custom-model/final/app/src/main/AndroidManifest.xml b/custom-model/final/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b807c63 --- /dev/null +++ b/custom-model/final/app/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/final/app/src/main/assets/labels.txt b/custom-model/final/app/src/main/assets/labels.txt old mode 100755 new mode 100644 similarity index 100% rename from final/app/src/main/assets/labels.txt rename to custom-model/final/app/src/main/assets/labels.txt diff --git a/final/app/src/main/assets/mountain.jpg b/custom-model/final/app/src/main/assets/mountain.jpg old mode 100755 new mode 100644 similarity index 100% rename from final/app/src/main/assets/mountain.jpg rename to custom-model/final/app/src/main/assets/mountain.jpg diff --git a/final/app/src/main/assets/tennis.jpg b/custom-model/final/app/src/main/assets/tennis.jpg old mode 100755 new mode 100644 similarity index 100% rename from final/app/src/main/assets/tennis.jpg rename to custom-model/final/app/src/main/assets/tennis.jpg diff --git a/final/app/src/main/java/com/google/firebase/codelab/mlkit/GraphicOverlay.java b/custom-model/final/app/src/main/java/com/google/firebase/codelab/mlkit_custommodel/GraphicOverlay.java old mode 100755 new mode 100644 similarity index 98% rename from final/app/src/main/java/com/google/firebase/codelab/mlkit/GraphicOverlay.java rename to custom-model/final/app/src/main/java/com/google/firebase/codelab/mlkit_custommodel/GraphicOverlay.java index d0661f1..58f12ca --- a/final/app/src/main/java/com/google/firebase/codelab/mlkit/GraphicOverlay.java +++ b/custom-model/final/app/src/main/java/com/google/firebase/codelab/mlkit_custommodel/GraphicOverlay.java @@ -11,11 +11,11 @@ // 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.google.firebase.codelab.mlkit; + +package com.google.firebase.codelab.mlkit_custommodel; import android.content.Context; import android.graphics.Canvas; -import android.hardware.Camera; import android.hardware.camera2.CameraCharacteristics; import android.util.AttributeSet; import android.view.View; diff --git a/custom-model/final/app/src/main/java/com/google/firebase/codelab/mlkit_custommodel/LabelGraphic.java b/custom-model/final/app/src/main/java/com/google/firebase/codelab/mlkit_custommodel/LabelGraphic.java new file mode 100644 index 0000000..4daf0dd --- /dev/null +++ b/custom-model/final/app/src/main/java/com/google/firebase/codelab/mlkit_custommodel/LabelGraphic.java @@ -0,0 +1,50 @@ +// Copyright 2018 Google LLC +// +// 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.google.firebase.codelab.mlkit_custommodel; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; + +import java.util.List; + +/** Graphic instance for rendering image labels. */ +public class LabelGraphic extends GraphicOverlay.Graphic { + + private final Paint textPaint; + private final GraphicOverlay overlay; + + private List labels; + + LabelGraphic(GraphicOverlay overlay, List labels) { + super(overlay); + this.overlay = overlay; + this.labels = labels; + textPaint = new Paint(); + textPaint.setColor(Color.WHITE); + textPaint.setTextSize(60.0f); + } + + @Override + public synchronized void draw(Canvas canvas) { + float x = overlay.getWidth() / 4.0f; + float y = overlay.getHeight() / 4.0f; + + for (String label : labels) { + canvas.drawText(label, x, y, textPaint); + y = y - 62.0f; + } + } +} diff --git a/custom-model/final/app/src/main/java/com/google/firebase/codelab/mlkit_custommodel/MainActivity.kt b/custom-model/final/app/src/main/java/com/google/firebase/codelab/mlkit_custommodel/MainActivity.kt new file mode 100644 index 0000000..b62c027 --- /dev/null +++ b/custom-model/final/app/src/main/java/com/google/firebase/codelab/mlkit_custommodel/MainActivity.kt @@ -0,0 +1,292 @@ +// Copyright 2018 Google LLC +// +// 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.google.firebase.codelab.mlkit_custommodel + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.os.Bundle +import android.util.Log +import android.util.Pair +import android.view.View +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.Toast + +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import com.google.firebase.ml.common.FirebaseMLException +import com.google.firebase.ml.common.modeldownload.FirebaseModelDownloadConditions +import com.google.firebase.ml.common.modeldownload.FirebaseModelManager +import com.google.firebase.ml.custom.* +import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine + +import java.io.BufferedReader +import java.io.InputStreamReader +import java.nio.ByteBuffer +import java.nio.ByteOrder +import kotlin.RuntimeException +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.experimental.and +import kotlin.math.max +import kotlin.math.min + + +class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener { + + /** Data structure holding pairs of for each inference result */ + data class LabelConfidence(val label: String, val confidence: Float) + + /** Current image being displayed in our app's screen */ + private var selectedImage: Bitmap? = null + + /** List of JPG files in our assets folder */ + private val imagePaths by lazy { + resources.assets.list("")!!.filter { it.endsWith(".jpg") } + } + + /** Labels corresponding to the output of the vision model. */ + private val labelList by lazy { + BufferedReader(InputStreamReader(resources.assets.open(LABEL_PATH))).lineSequence().toList() + } + + /** Preallocated buffers for storing image data. */ + private val imageBuffer = IntArray(DIM_IMG_SIZE_X * DIM_IMG_SIZE_Y) + + // Gets the targeted width / height. + private val targetedWidthHeight: Pair + get() { + val targetWidth: Int + val targetHeight: Int + val maxWidthForPortraitMode = image_view.width + val maxHeightForPortraitMode = image_view.height + targetWidth = maxWidthForPortraitMode + targetHeight = maxHeightForPortraitMode + return Pair(targetWidth, targetHeight) + } + + /** Input options used for our Firebase model interpreter */ + private val modelInputOutputOptions by lazy { + val inputDims = arrayOf(DIM_BATCH_SIZE, DIM_IMG_SIZE_X, DIM_IMG_SIZE_Y, DIM_PIXEL_SIZE) + val outputDims = arrayOf(DIM_BATCH_SIZE, labelList.size) + FirebaseModelInputOutputOptions.Builder() + .setInputFormat(0, FirebaseModelDataType.BYTE, inputDims.toIntArray()) + .setOutputFormat(0, FirebaseModelDataType.BYTE, outputDims.toIntArray()) + .build() + } + + /** Firebase model interpreter used for the local model from assets */ + private lateinit var modelInterpreter: FirebaseModelInterpreter + + /** Initialize a local model interpreter from assets file */ + private fun createLocalModelInterpreter(): FirebaseModelInterpreter { + // Select the first available .tflite file as our local model + val localModelName = resources.assets.list("")?.firstOrNull { it.endsWith(".tflite") } + ?: throw(RuntimeException("Don't forget to add the tflite file to your assets folder")) + Log.d(TAG, "Local model found: $localModelName") + + // Create an interpreter with the local model asset + val localModel = + FirebaseCustomLocalModel.Builder().setAssetFilePath(localModelName).build() + val localInterpreter = FirebaseModelInterpreter.getInstance( + FirebaseModelInterpreterOptions.Builder(localModel).build())!! + Log.d(TAG, "Local model interpreter initialized") + + // Return the interpreter + return localInterpreter + } + + /** Initialize a remote model interpreter from Firebase server */ + private suspend fun createRemoteModelInterpreter(): FirebaseModelInterpreter { + return suspendCancellableCoroutine { cont -> + runOnUiThread { + Toast.makeText(baseContext, "Downloading model...", Toast.LENGTH_LONG).show() + } + + // Define conditions required for our model to be downloaded. We only request Wi-Fi. + val conditions = + FirebaseModelDownloadConditions.Builder().requireWifi().build() + + // Build a remote model object by specifying the name you assigned the model + // when you uploaded it in the Firebase console. + val remoteModel = + FirebaseCustomRemoteModel.Builder(REMOTE_MODEL_NAME).build() + val manager = FirebaseModelManager.getInstance() + manager.download(remoteModel, conditions).addOnCompleteListener { + if (!it.isSuccessful) cont.resumeWithException( + RuntimeException("Remote model failed to download", it.exception)) + + val msg = "Remote model successfully downloaded" + runOnUiThread { Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() } + Log.d(TAG, msg) + + val remoteInterpreter = FirebaseModelInterpreter.getInstance( + FirebaseModelInterpreterOptions.Builder(remoteModel).build())!! + Log.d(TAG, "Remote model interpreter initialized") + + // Return the interpreter via continuation object + cont.resume(remoteInterpreter) + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + val adapter = ArrayAdapter( + this, + android.R.layout.simple_spinner_dropdown_item, + imagePaths.mapIndexed { idx, _ -> "Image ${idx + 1}" }) + + spinner.adapter = adapter + spinner.onItemSelectedListener = this + button_run.setOnClickListener { runModelInference() } + + // Disable the inference button until model is loaded + button_run.isEnabled = false + + // Load the model interpreter in a coroutine + lifecycleScope.launch(Dispatchers.IO) { + //modelInterpreter = createLocalModelInterpreter() + //modelInterpreter = createRemoteModelInterpreter() + runOnUiThread { button_run.isEnabled = true } + } + + } + + /** Uses model to make predictions and interpret output into likely labels. */ + private fun runModelInference() = selectedImage?.let { image -> + + // Create input data. + val imgData = convertBitmapToByteBuffer(image) + + try { + // Create model inputs from our image data. + val modelInputs = FirebaseModelInputs.Builder().add(imgData).build() + + // Perform inference using our model interpreter. + modelInterpreter.run(modelInputs, modelInputOutputOptions).continueWith { + val inferenceOutput = it.result?.getOutput>(0)!! + + // Display labels on the screen using an overlay + val topLabels = getTopLabels(inferenceOutput) + graphic_overlay.clear() + graphic_overlay.add(LabelGraphic(graphic_overlay, topLabels)) + topLabels + } + + } catch (exc: FirebaseMLException) { + val msg = "Error running model inference" + Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() + Log.e(TAG, msg, exc) + } + } + + /** Gets the top labels in the results. */ + @Synchronized + private fun getTopLabels(inferenceOutput: Array): List { + // Since we ran inference on a single image, inference output will have a single row. + val imageInference = inferenceOutput.first() + + // The columns of the image inference correspond to the confidence for each label. + return labelList.mapIndexed { idx, label -> + LabelConfidence(label, (imageInference[idx] and 0xFF.toByte()) / 255.0f) + + // Sort the results in decreasing order of confidence and return only top 3. + }.sortedBy { it.confidence }.reversed().map { "${it.label}:${it.confidence}" } + .subList(0, min(labelList.size, RESULTS_TO_SHOW)) + } + + /** Writes Image data into a `ByteBuffer`. */ + @Synchronized + private fun convertBitmapToByteBuffer(bitmap: Bitmap): ByteBuffer { + val imgData = ByteBuffer.allocateDirect( + DIM_BATCH_SIZE * DIM_IMG_SIZE_X * DIM_IMG_SIZE_Y * DIM_PIXEL_SIZE).apply { + order(ByteOrder.nativeOrder()) + rewind() + } + val scaledBitmap = + Bitmap.createScaledBitmap(bitmap, DIM_IMG_SIZE_X, DIM_IMG_SIZE_Y, true) + scaledBitmap.getPixels( + imageBuffer, 0, scaledBitmap.width, 0, 0, scaledBitmap.width, scaledBitmap.height) + // Convert the image to int points. + var pixel = 0 + for (i in 0 until DIM_IMG_SIZE_X) { + for (j in 0 until DIM_IMG_SIZE_Y) { + val `val` = imageBuffer[pixel++] + imgData.put((`val` shr 16 and 0xFF).toByte()) + imgData.put((`val` shr 8 and 0xFF).toByte()) + imgData.put((`val` and 0xFF).toByte()) + } + } + return imgData + } + + override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) { + graphic_overlay.clear() + selectedImage = decodeBitmapAsset(this, imagePaths[position]) + if (selectedImage != null) { + // Get the dimensions of the View + val targetedSize = targetedWidthHeight + + val targetWidth = targetedSize.first + val maxHeight = targetedSize.second + + // Determine how much to scale down the image + val scaleFactor = max( + selectedImage!!.width.toFloat() / targetWidth.toFloat(), + selectedImage!!.height.toFloat() / maxHeight.toFloat()) + + val resizedBitmap = Bitmap.createScaledBitmap( + selectedImage!!, + (selectedImage!!.width / scaleFactor).toInt(), + (selectedImage!!.height / scaleFactor).toInt(), + true) + + image_view.setImageBitmap(resizedBitmap) + selectedImage = resizedBitmap + } + } + + override fun onNothingSelected(parent: AdapterView<*>) = Unit + + companion object { + private val TAG = MainActivity::class.java.simpleName + + /** Name of the label file stored in Assets. */ + private const val LABEL_PATH = "labels.txt" + + /** Name of the remote model in Firebase. */ + private const val REMOTE_MODEL_NAME = "mobilenet_v1_224_quant" + + /** Number of results to show in the UI. */ + private const val RESULTS_TO_SHOW = 3 + + /** Dimensions of inputs. */ + private const val DIM_BATCH_SIZE = 1 + private const val DIM_PIXEL_SIZE = 3 + private const val DIM_IMG_SIZE_X = 224 + private const val DIM_IMG_SIZE_Y = 224 + + /** Utility function for loading and resizing images from app asset folder. */ + fun decodeBitmapAsset(context: Context, filePath: String): Bitmap = + context.assets.open(filePath).let { BitmapFactory.decodeStream(it) } + } +} diff --git a/custom-model/final/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/custom-model/final/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/custom-model/final/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/custom-model/final/app/src/main/res/drawable/ic_launcher_background.xml b/custom-model/final/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..0d025f9 --- /dev/null +++ b/custom-model/final/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/custom-model/final/app/src/main/res/layout/activity_main.xml b/custom-model/final/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..55e3cce --- /dev/null +++ b/custom-model/final/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,49 @@ + + + + + + + + + +