1. Antes de comenzar
En este codelab, aprenderás a ejecutar una inferencia de detección de objetos desde una app para Android con TensorFlow Serving a través de REST y gRPC.
Requisitos previos
- Conocimientos básicos sobre el desarrollo de Android con Java
- Conocimientos básicos del aprendizaje automático con TensorFlow, como el entrenamiento y la implementación
- Conocimientos básicos de las terminales y Docker
Qué aprenderás
- Cómo encontrar modelos de detección de objetos previamente entrenados en TensorFlow Hub
- Cómo crear una app sencilla para Android y realizar predicciones con el modelo de detección de objetos descargado a través de TensorFlow Serving (REST y gRPC)
- Cómo renderizar el resultado de la detección en la IU
Requisitos
- La versión más reciente de Android Studio
- Docker
- Bash
2. Prepárate
Para descargar el código de este codelab, haz lo siguiente:
- Navega al repositorio de GitHub de este codelab.
- Haz clic en Code > Download ZIP para descargar todo el código de este codelab.

- Descomprime el archivo ZIP descargado para desempaquetar una carpeta raíz
codelabscon todos los recursos que necesitas.
Para este codelab, solo necesitas los archivos del subdirectorio TFServing/ObjectDetectionAndroid en el repositorio, que contiene dos carpetas:
- La carpeta
startercontiene el código de inicio en el que se basa este codelab. - La carpeta
finishedcontiene el código completado de la app de ejemplo finalizada.
3. Agrega las dependencias al proyecto
Cómo importar la app de partida a Android Studio
- En Android Studio, haz clic en File > New > Import project y, luego, elige la carpeta
starterdel código fuente que descargaste antes.
Agrega las dependencias de OkHttp y gRPC
- En el archivo
app/build.gradlede tu proyecto, confirma la presencia de las dependencias.
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'
}
Sincroniza tu proyecto con archivos de Gradle
- Selecciona
Sync Project with Gradle Files en el menú de navegación.
4. Ejecuta la app de partida
- Inicia Android Emulator y, luego, haz clic en
Run 'app' en el menú de navegación.
Ejecuta y explora la app
La app debería iniciarse en tu dispositivo Android. La IU es bastante sencilla: hay una imagen de un gato en la que deseas detectar objetos, y el usuario puede elegir la forma de enviar los datos al backend, con REST o gRPC. El backend realiza la detección de objetos en la imagen y devuelve los resultados de la detección a la app cliente, que vuelve a renderizar la IU.

En este momento, si haces clic en Run inference, no sucederá nada. Esto se debe a que aún no puede comunicarse con el backend.
5. Implementa un modelo de detección de objetos con TensorFlow Serving
La detección de objetos es una tarea de AA muy común cuyo objetivo es detectar objetos en imágenes, es decir, predecir posibles categorías de los objetos y cuadros de límite a su alrededor. Este es un ejemplo de un resultado de detección:

Google publicó varios modelos previamente entrenados en TensorFlow Hub. Para ver la lista completa, visita la página object_detection. En este codelab, usarás el modelo SSD MobileNet V2 FPNLite 320x320, que es relativamente ligero, por lo que no es necesario que uses una GPU para ejecutarlo.
Para implementar el modelo de detección de objetos con TensorFlow Serving, haz lo siguiente:
- Descarga el archivo del modelo.
- Descomprime el archivo
.tar.gzdescargado con una herramienta de descompresión, como 7-Zip. - Crea una carpeta
ssd_mobilenet_v2_2_320y, luego, crea una subcarpeta123dentro de ella. - Coloca la carpeta
variablesextraída y el archivosaved_model.pben la subcarpeta123.
Puedes consultar la carpeta ssd_mobilenet_v2_2_320 como la carpeta SavedModel. 123 es un ejemplo de número de versión. Si lo deseas, puedes elegir otro número.
La estructura de carpetas debería verse como en la siguiente imagen:

Inicia TensorFlow Serving
- En tu terminal, inicia TensorFlow Serving con Docker, pero reemplaza el marcador de posición
PATH/TO/SAVEDMODELpor la ruta de acceso absoluta de la carpetassd_mobilenet_v2_2_320en tu computadora.
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 descarga automáticamente la imagen de TensorFlow Serving primero, lo cual tarda un minuto. Luego, TensorFlow Serving debería iniciarse. El registro debería verse como este fragmento de código:
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. Conecta la app para Android con TensorFlow Serving a través de REST
Ahora el backend está preparado, por lo que puedes enviar solicitudes de clientes a TensorFlow Serving para detectar objetos en imágenes. Existen dos métodos de enviar solicitudes a TensorFlow Serving:
- REST
- gRPC
Envía solicitudes y recibe respuestas a través de REST
Sigue estos tres pasos simples:
- Crea la solicitud REST.
- Envía la solicitud REST a TensorFlow Serving.
- Extrae el resultado previsto de la respuesta de REST y renderiza la IU.
Lograrás estos objetivos en MainActivity.java.
Crea la solicitud de REST
En este momento, hay una función createRESTRequest() vacía en el archivo MainActivity.java. Implementas esta función para crear una solicitud de REST.
private Request createRESTRequest() {
}
TensorFlow Serving espera una solicitud POST que contenga el tensor de imagen para el modelo SSD MobileNet que usas, por lo que debes extraer los valores RGB de cada píxel de la imagen en un array y, luego, incluir el array en un JSON, que es la carga útil de la solicitud.
- Agrega este código a la función
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;
Envía la solicitud de REST a TensorFlow Serving
La app permite que el usuario elija REST o gRPC para comunicarse con TensorFlow Serving, por lo que hay dos ramas en el objeto de escucha onClick(View view).
predictButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (requestRadioGroup.getCheckedRadioButtonId() == R.id.rest) {
// TODO: REST request
}
else {
}
}
}
)
- Agrega este código a la rama de REST del objeto de escucha
onClick(View view)para usar OkHttp y enviar la solicitud a 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;
}
Procesa la respuesta de REST de TensorFlow Serving
El modelo SSD MobileNet devuelve una cantidad de resultados, que incluyen lo siguiente:
num_detections: Es la cantidad de detecciones.detection_scores: Puntuaciones de deteccióndetection_classes: Es el índice de la clase de detección.detection_boxes: Las coordenadas del cuadro delimitador
Implementas la función postprocessRESTResponse() para controlar la respuesta.
private void postprocessRESTResponse(Predict.PredictResponse response) {
}
- Agrega este código a la función
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);
Ahora, la función de procesamiento posterior extrae los valores predichos de la respuesta, identifica la categoría más probable del objeto y las coordenadas de los vértices del cuadro delimitador y, por último, renderiza el cuadro delimitador de detección en la IU.
Ejecución
- Haz clic en
Run 'app' en el menú de navegación y, luego, espera a que se cargue la app. - Selecciona REST > Run inference.
La app tarda unos segundos en renderizar el cuadro de límite del gato y mostrar 17 como la categoría del objeto, que se asigna al objeto cat en el conjunto de datos de COCO.

7. Conecta la app para Android con TensorFlow Serving a través de gRPC
Además de REST, TensorFlow Serving también admite gRPC.

gRPC es un framework de llamada de procedimiento remoto (RPC) moderno de código abierto y alto rendimiento, que se puede ejecutar en cualquier entorno. Puede conectar de forma eficaz servicios en centros de datos (y entre ellos) y ofrece compatibilidad conectable con balanceo de cargas, seguimiento, verificación de estado y autenticación. Se observó que gRPC tiene un mejor rendimiento que REST en la práctica.
Envía solicitudes y recibe respuestas con gRPC
Hay cuatro pasos sencillos:
- [Opcional] Genera el código de stub del cliente de gRPC.
- Crea la solicitud de gRPC.
- Envía la solicitud de gRPC a TensorFlow Serving.
- Extrae el resultado previsto de la respuesta de gRPC y renderiza la IU.
Lograrás estos objetivos en MainActivity.java.
Genera el código de stub del cliente de gRPC (opcional)
Para usar gRPC con TensorFlow Serving, debes seguir el flujo de trabajo de gRPC. Si quieres obtener más información, consulta la documentación de gRPC.

TensorFlow Serving y TensorFlow definen los archivos .proto por ti. A partir de TensorFlow y TensorFlow Serving 2.8, se necesitan estos archivos .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
- Para generar el stub, agrega este código al archivo
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' }
}
}
}
}
Crea la solicitud de gRPC
Al igual que con la solicitud de REST, debes crear la solicitud de gRPC en la función createGRPCRequest().
private Request createGRPCRequest() {
}
- Agrega este código a la función
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();
Envía la solicitud de gRPC a TensorFlow Serving
Ahora puedes finalizar el objeto de escucha onClick(View view).
predictButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (requestRadioGroup.getCheckedRadioButtonId() == R.id.rest) {
}
else {
// TODO: gRPC request
}
}
}
)
- Agrega este código a la rama de 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;
}
Procesa la respuesta de gRPC de TensorFlow Serving
Al igual que con gRPC, implementas la función postprocessGRPCResponse() para controlar la respuesta.
private void postprocessGRPCResponse(Predict.PredictResponse response) {
}
- Agrega este código a la función
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);
Ahora, la función de procesamiento posterior puede extraer los valores previstos de la respuesta y renderizar el cuadro delimitador de detección en la IU.
Ejecución
- Haz clic en
Run 'app' en el menú de navegación y, luego, espera a que se cargue la app. - Selecciona gRPC > Run inference.
La app tarda unos segundos en renderizar el cuadro delimitador del gato y mostrar 17 como la categoría del objeto, que se asigna a la categoría cat en el conjunto de datos COCO.
8. Felicitaciones
Usaste TensorFlow Serving para agregar capacidades de detección de objetos a tu app.