Создайте приложение для Android для обнаружения объектов на изображениях
О практической работе
1. Прежде чем вы начнете
В этой лабораторной работе вы узнаете, как выполнить вывод об обнаружении объектов из приложения Android, используя TensorFlow Serving с REST и gRPC.
Предпосылки
- Базовые знания Android-разработки с использованием Java.
- Базовые знания машинного обучения с TensorFlow, такие как обучение и развертывание
- Базовые знания терминалов и Docker
Что вы узнаете
- Как найти предварительно обученные модели обнаружения объектов в TensorFlow Hub.
- Как создать простое приложение для Android и делать прогнозы с помощью загруженной модели обнаружения объектов через TensorFlow Serving (REST и gRPC).
- Как отобразить результат обнаружения в пользовательском интерфейсе.
Что вам понадобится
- Последняя версия Android Studio
- Докер
- Баш
2. Настроить
Чтобы скачать код для этой кодлабы:
- Перейдите в репозиторий GitHub для этой лаборатории кода.
- Нажмите « Код» > «Загрузить zip» , чтобы загрузить весь код для этой лаборатории кода.
- Разархивируйте загруженный zip-файл, чтобы распаковать корневую папку
codelabs
со всеми необходимыми ресурсами.
Для этой кодлабы вам нужны только файлы в подкаталоге TFServing/ObjectDetectionAndroid
в репозитории, который содержит две папки:
-
starter
папка содержит начальный код, на основе которого вы строите эту лабораторию кода. -
finished
папка содержит завершенный код для готового примера приложения.
3. Добавьте зависимости в проект
Импортируйте стартовое приложение в Android Studio.
- В Android Studio нажмите « Файл» > «Создать» > «Импортировать проект», а затем выберите
starter
папку из загруженного ранее исходного кода.
Добавьте зависимости для OkHttp и gRPC.
- В файле
app/build.gradle
вашего проекта подтвердите наличие зависимостей.
dependencies {
// ...
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'javax.annotation:javax.annotation-api:1.3.2'
implementation 'io.grpc:grpc-okhttp:1.29.0'
implementation 'io.grpc:grpc-protobuf-lite:1.29.0'
implementation 'io.grpc:grpc-stub:1.29.0'
}
Синхронизируйте свой проект с файлами Gradle
- Выбирать
Синхронизируйте проект с файлами Gradle из меню навигации.
4. Запустите стартовое приложение
- Запустите эмулятор Android и нажмите
Запустите «приложение» в меню навигации.
Запустите и исследуйте приложение
Приложение должно запуститься на вашем Android-устройстве. Пользовательский интерфейс довольно прост: есть изображение кошки, на котором вы хотите обнаруживать объекты, и пользователь может выбрать способ отправки данных на серверную часть с помощью REST или gRPC. Серверная часть выполняет обнаружение объектов на изображении и возвращает результаты обнаружения клиентскому приложению, которое снова отображает пользовательский интерфейс.
Прямо сейчас, если вы нажмете Run inference , ничего не произойдет. Это связано с тем, что он еще не может взаимодействовать с серверной частью.
5. Развертывание модели обнаружения объектов с помощью TensorFlow Serving
Обнаружение объектов — очень распространенная задача машинного обучения, и ее цель — обнаружить объекты на изображениях, а именно предсказать возможные категории объектов и ограничивающие рамки вокруг них. Вот пример результата обнаружения:
Google опубликовал ряд предварительно обученных моделей на TensorFlow Hub . Чтобы увидеть полный список, посетите страницу object_detection . Вы используете относительно легкую модель SSD MobileNet V2 FPNLite 320x320 для этой лаборатории кода, поэтому вам не обязательно использовать графический процессор для ее запуска.
Чтобы развернуть модель обнаружения объектов с помощью TensorFlow Serving:
- Загрузите файл модели.
- Распакуйте загруженный файл
.tar.gz
с помощью инструмента распаковки, например 7-Zip. - Создайте папку
ssd_mobilenet_v2_2_320
, а затем создайте в ней подпапку123
. - Поместите извлеченную папку
variables
и файлsaved_model.pb
в подпапку123
.
Вы можете ссылаться на папку ssd_mobilenet_v2_2_320
как на папку SavedModel
. 123
— пример номера версии. Если хотите, можете выбрать другой номер.
Структура папок должна выглядеть так:
Запустить обслуживание TensorFlow
- В терминале запустите TensorFlow Serving с Docker, но замените заполнитель
PATH/TO/SAVEDMODEL
на абсолютный путь к папкеssd_mobilenet_v2_2_320
на вашем компьютере.
docker pull tensorflow/serving docker run -it --rm -p 8500:8500 -p 8501:8501 -v "PATH/TO/SAVEDMODEL:/models/ssd_mobilenet_v2_2" -e MODEL_NAME=ssd_mobilenet_v2_2 tensorflow/serving
Docker сначала автоматически загружает образ TensorFlow Serving, что занимает минуту. После этого должен запуститься TensorFlow Serving. Журнал должен выглядеть как этот фрагмент кода:
2022-02-25 06:01:12.513231: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:206] Restoring SavedModel bundle. 2022-02-25 06:01:12.585012: I external/org_tensorflow/tensorflow/core/platform/profile_utils/cpu_utils.cc:114] CPU Frequency: 3000000000 Hz 2022-02-25 06:01:13.395083: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:190] Running initialization op on SavedModel bundle at path: /models/ssd_mobilenet_v2_2/123 2022-02-25 06:01:13.837562: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:277] SavedModel load for tags { serve }; Status: success: OK. Took 1928700 microseconds. 2022-02-25 06:01:13.877848: I tensorflow_serving/servables/tensorflow/saved_model_warmup_util.cc:59] No warmup data file found at /models/ssd_mobilenet_v2_2/123/assets.extra/tf_serving_warmup_requests 2022-02-25 06:01:13.929844: I tensorflow_serving/core/loader_harness.cc:87] Successfully loaded servable version {name: ssd_mobilenet_v2_2 version: 123} 2022-02-25 06:01:13.985848: I tensorflow_serving/model_servers/server_core.cc:486] Finished adding/updating models 2022-02-25 06:01:13.985987: I tensorflow_serving/model_servers/server.cc:367] Profiler service is enabled 2022-02-25 06:01:13.988994: I tensorflow_serving/model_servers/server.cc:393] Running gRPC ModelServer at 0.0.0.0:8500 ... [warn] getaddrinfo: address family for nodename not supported 2022-02-25 06:01:14.033872: I tensorflow_serving/model_servers/server.cc:414] Exporting HTTP/REST API at:localhost:8501 ... [evhttp_server.cc : 245] NET_LOG: Entering the event loop ...
6. Подключите приложение Android к TensorFlow Serving через REST.
Теперь серверная часть готова, поэтому вы можете отправлять клиентские запросы в TensorFlow Serving для обнаружения объектов на изображениях. Есть два способа отправки запросов в TensorFlow Serving:
- ОТДЫХАТЬ
- gRPC
Отправляйте запросы и получайте ответы через REST
Есть три простых шага:
- Создайте REST-запрос.
- Отправьте запрос REST в TensorFlow Serving.
- Извлеките прогнозируемый результат из ответа REST и визуализируйте пользовательский интерфейс.
Вы добьетесь этого в MainActivity.java.
Создайте REST-запрос
Прямо сейчас в файле MainActivity.java есть пустая функция MainActivity.java
createRESTRequest()
. Вы реализуете эту функцию для создания запроса REST.
private Request createRESTRequest() {
}
TensorFlow Serving ожидает запрос POST, который содержит тензор изображения для модели SSD MobileNet, которую вы используете, поэтому вам нужно извлечь значения RGB из каждого пикселя изображения в массив, а затем обернуть массив в JSON, который является полезной нагрузкой. запроса.
- Добавьте этот код в
createRESTRequest()
:
//Create the REST request.
int[] inputImg = new int[INPUT_IMG_HEIGHT * INPUT_IMG_WIDTH];
int[][][][] inputImgRGB = new int[1][INPUT_IMG_HEIGHT][INPUT_IMG_WIDTH][3];
inputImgBitmap.getPixels(inputImg, 0, INPUT_IMG_WIDTH, 0, 0, INPUT_IMG_WIDTH, INPUT_IMG_HEIGHT);
int pixel;
for (int i = 0; i < INPUT_IMG_HEIGHT; i++) {
for (int j = 0; j < INPUT_IMG_WIDTH; j++) {
// Extract RBG values from each pixel; alpha is ignored
pixel = inputImg[i * INPUT_IMG_WIDTH + j];
inputImgRGB[0][i][j][0] = ((pixel >> 16) & 0xff);
inputImgRGB[0][i][j][1] = ((pixel >> 8) & 0xff);
inputImgRGB[0][i][j][2] = ((pixel) & 0xff);
}
}
RequestBody requestBody =
RequestBody.create("{\"instances\": " + Arrays.deepToString(inputImgRGB) + "}", JSON);
Request request =
new Request.Builder()
.url("http://" + SERVER + ":" + REST_PORT + "/v1/models/" + MODEL_NAME + ":predict")
.post(requestBody)
.build();
return request;
Отправьте запрос REST в TensorFlow Serving.
Приложение позволяет пользователю выбрать REST или gRPC для связи с TensorFlow Serving, поэтому в прослушивателе onClick(View view)
есть две ветви.
predictButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (requestRadioGroup.getCheckedRadioButtonId() == R.id.rest) {
// TODO: REST request
}
else {
}
}
}
)
- Добавьте этот код в ветку REST прослушивателя onClick
onClick(View view)
, чтобы использовать OkHttp для отправки запроса в TensorFlow Serving:
// Send the REST request.
Request request = createRESTRequest();
try {
client =
new OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.callTimeout(20, TimeUnit.SECONDS)
.build();
Response response = client.newCall(request).execute();
JSONObject responseObject = new JSONObject(response.body().string());
postprocessRESTResponse(responseObject);
} catch (IOException | JSONException e) {
Log.e(TAG, e.getMessage());
responseTextView.setText(e.getMessage());
return;
}
Обработка ответа REST от TensorFlow Serving
Модель SSD MobileNet возвращает ряд результатов, в том числе:
-
num_detections
: количество обнаружений - discovery_scores : оценки
detection_scores
-
detection_classes
: индекс класса обнаружения -
detection_boxes
: координаты ограничивающей рамки
Вы реализуете функцию postprocessRESTResponse()
для обработки ответа.
private void postprocessRESTResponse(Predict.PredictResponse response) {
}
- Добавьте этот код в функцию
postprocessRESTResponse()
:
// Process the REST response.
JSONArray predictionsArray = responseObject.getJSONArray("predictions");
//You only send one image, so you directly extract the first element.
JSONObject predictions = predictionsArray.getJSONObject(0);
// Argmax
int maxIndex = 0;
JSONArray detectionScores = predictions.getJSONArray("detection_scores");
for (int j = 0; j < predictions.getInt("num_detections"); j++) {
maxIndex =
detectionScores.getDouble(j) > detectionScores.getDouble(maxIndex + 1) ? j : maxIndex;
}
int detectionClass = predictions.getJSONArray("detection_classes").getInt(maxIndex);
JSONArray boundingBox = predictions.getJSONArray("detection_boxes").getJSONArray(maxIndex);
double ymin = boundingBox.getDouble(0);
double xmin = boundingBox.getDouble(1);
double ymax = boundingBox.getDouble(2);
double xmax = boundingBox.getDouble(3);
displayResult(detectionClass, (float) ymin, (float) xmin, (float) ymax, (float) xmax);
Теперь функция постобработки извлекает предсказанные значения из ответа, определяет наиболее вероятную категорию объекта и координаты вершин ограничивающей рамки и, наконец, отображает ограничивающую рамку обнаружения в пользовательском интерфейсе.
Запустить его
- Нажмите
Запустите «приложение» в меню навигации и подождите, пока приложение загрузится.
- Выберите REST > Выполнить вывод .
Требуется несколько секунд, прежде чем приложение отобразит ограничительную рамку кошки и отобразит 17
в качестве категории объекта, которая сопоставляется с объектом cat
в наборе данных COCO .
7. Подключите приложение Android к TensorFlow Serving через gRPC.
Помимо REST, TensorFlow Serving также поддерживает gRPC .
gRPC — это современная высокопроизводительная платформа удаленного вызова процедур (RPC) с открытым исходным кодом, которая может работать в любой среде. Он может эффективно подключать службы в центрах обработки данных и между ними с подключаемой поддержкой балансировки нагрузки, трассировки, проверки работоспособности и аутентификации. Было замечено, что на практике gRPC более эффективен, чем REST.
Отправляйте запросы и получайте ответы с помощью gRPC
Всего четыре простых шага:
- [Необязательно] Создайте код-заглушку клиента gRPC.
- Создайте запрос gRPC.
- Отправьте запрос gRPC в TensorFlow Serving.
- Извлеките прогнозируемый результат из ответа gRPC и визуализируйте пользовательский интерфейс.
Вы добьетесь этого в MainActivity.java.
Необязательно: сгенерируйте код-заглушку клиента gRPC.
Чтобы использовать gRPC с TensorFlow Serving, вам необходимо следовать рабочему процессу gRPC. Чтобы узнать больше о деталях, см. документацию по gRPC .
TensorFlow Serving и TensorFlow определяют для вас файлы .proto
. Начиная с TensorFlow и TensorFlow Serving 2.8, необходимы эти файлы .proto
:
tensorflow/core/example/example.proto
tensorflow/core/example/feature.proto
tensorflow/core/protobuf/struct.proto
tensorflow/core/protobuf/saved_object_graph.proto
tensorflow/core/protobuf/saver.proto
tensorflow/core/protobuf/trackable_object_graph.proto
tensorflow/core/protobuf/meta_graph.proto
tensorflow/core/framework/node_def.proto
tensorflow/core/framework/attr_value.proto
tensorflow/core/framework/function.proto
tensorflow/core/framework/types.proto
tensorflow/core/framework/tensor_shape.proto
tensorflow/core/framework/full_type.proto
tensorflow/core/framework/versions.proto
tensorflow/core/framework/op_def.proto
tensorflow/core/framework/graph.proto
tensorflow/core/framework/tensor.proto
tensorflow/core/framework/resource_handle.proto
tensorflow/core/framework/variable.proto
tensorflow_serving/apis/inference.proto
tensorflow_serving/apis/classification.proto
tensorflow_serving/apis/predict.proto
tensorflow_serving/apis/regression.proto
tensorflow_serving/apis/get_model_metadata.proto
tensorflow_serving/apis/input.proto
tensorflow_serving/apis/prediction_service.proto
tensorflow_serving/apis/model.proto
- Чтобы сгенерировать заглушку, добавьте этот код в файл
app/build.gradle
.
apply plugin: 'com.google.protobuf'
protobuf {
protoc { artifact = 'com.google.protobuf:protoc:3.11.0' }
plugins {
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.29.0'
}
}
generateProtoTasks {
all().each { task ->
task.builtins {
java { option 'lite' }
}
task.plugins {
grpc { option 'lite' }
}
}
}
}
Создайте запрос gRPC
Подобно запросу REST, вы создаете запрос gRPC в функции createGRPCRequest()
.
private Request createGRPCRequest() {
}
- Добавьте этот код в
createGRPCRequest()
:
if (stub == null) {
channel = ManagedChannelBuilder.forAddress(SERVER, GRPC_PORT).usePlaintext().build();
stub = PredictionServiceGrpc.newBlockingStub(channel);
}
Model.ModelSpec.Builder modelSpecBuilder = Model.ModelSpec.newBuilder();
modelSpecBuilder.setName(MODEL_NAME);
modelSpecBuilder.setVersion(Int64Value.of(MODEL_VERSION));
modelSpecBuilder.setSignatureName(SIGNATURE_NAME);
Predict.PredictRequest.Builder builder = Predict.PredictRequest.newBuilder();
builder.setModelSpec(modelSpecBuilder);
TensorProto.Builder tensorProtoBuilder = TensorProto.newBuilder();
tensorProtoBuilder.setDtype(DataType.DT_UINT8);
TensorShapeProto.Builder tensorShapeBuilder = TensorShapeProto.newBuilder();
tensorShapeBuilder.addDim(TensorShapeProto.Dim.newBuilder().setSize(1));
tensorShapeBuilder.addDim(TensorShapeProto.Dim.newBuilder().setSize(INPUT_IMG_HEIGHT));
tensorShapeBuilder.addDim(TensorShapeProto.Dim.newBuilder().setSize(INPUT_IMG_WIDTH));
tensorShapeBuilder.addDim(TensorShapeProto.Dim.newBuilder().setSize(3));
tensorProtoBuilder.setTensorShape(tensorShapeBuilder.build());
int[] inputImg = new int[INPUT_IMG_HEIGHT * INPUT_IMG_WIDTH];
inputImgBitmap.getPixels(inputImg, 0, INPUT_IMG_WIDTH, 0, 0, INPUT_IMG_WIDTH, INPUT_IMG_HEIGHT);
int pixel;
for (int i = 0; i < INPUT_IMG_HEIGHT; i++) {
for (int j = 0; j < INPUT_IMG_WIDTH; j++) {
// Extract RBG values from each pixel; alpha is ignored.
pixel = inputImg[i * INPUT_IMG_WIDTH + j];
tensorProtoBuilder.addIntVal((pixel >> 16) & 0xff);
tensorProtoBuilder.addIntVal((pixel >> 8) & 0xff);
tensorProtoBuilder.addIntVal((pixel) & 0xff);
}
}
TensorProto tensorProto = tensorProtoBuilder.build();
builder.putInputs("input_tensor", tensorProto);
builder.addOutputFilter("num_detections");
builder.addOutputFilter("detection_boxes");
builder.addOutputFilter("detection_classes");
builder.addOutputFilter("detection_scores");
return builder.build();
Отправьте запрос gRPC в TensorFlow Serving.
Теперь вы можете закончить прослушиватель onClick(View view)
.
predictButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (requestRadioGroup.getCheckedRadioButtonId() == R.id.rest) {
}
else {
// TODO: gRPC request
}
}
}
)
- Добавьте этот код в ветку gRPC:
try {
Predict.PredictRequest request = createGRPCRequest();
Predict.PredictResponse response = stub.predict(request);
postprocessGRPCResponse(response);
} catch (Exception e) {
Log.e(TAG, e.getMessage());
responseTextView.setText(e.getMessage());
return;
}
Обработка ответа gRPC от TensorFlow Serving
Подобно gRPC, вы реализуете функцию postprocessGRPCResponse postprocessGRPCResponse()
для обработки ответа.
private void postprocessGRPCResponse(Predict.PredictResponse response) {
}
- Добавьте этот код в функцию
postprocessGRPCResponse()
:
// Process the response.
float numDetections = response.getOutputsMap().get("num_detections").getFloatValList().get(0);
List<Float> detectionScores = response.getOutputsMap().get("detection_scores").getFloatValList();
int maxIndex = 0;
for (int j = 0; j < numDetections; j++) {
maxIndex = detectionScores.get(j) > detectionScores.get(maxIndex + 1) ? j : maxIndex;
}
Float detectionClass = response.getOutputsMap().get("detection_classes").getFloatValList().get(maxIndex);
List<Float> boundingBoxValues = response.getOutputsMap().get("detection_boxes").getFloatValList();
float ymin = boundingBoxValues.get(maxIndex * 4);
float xmin = boundingBoxValues.get(maxIndex * 4 + 1);
float ymax = boundingBoxValues.get(maxIndex * 4 + 2);
float xmax = boundingBoxValues.get(maxIndex * 4 + 3);
displayResult(detectionClass.intValue(), ymin, xmin, ymax, xmax);
Теперь функция постобработки может извлекать предсказанные значения из ответа и отображать ограничивающую рамку обнаружения в пользовательском интерфейсе.
Запустить его
- Нажмите
Запустите «приложение» в меню навигации и подождите, пока приложение загрузится.
- Выберите gRPC > Выполнить вывод .
Требуется несколько секунд, прежде чем приложение отобразит ограничительную рамку кошки и отобразит 17
в качестве категории объекта, которая сопоставляется с категорией cat
в наборе данных COCO .
8. Поздравления
Вы использовали TensorFlow Serving, чтобы добавить в свое приложение возможности обнаружения объектов!