Google Cloud Vision sobre gRPC en Delphi

· Componentes

Google Cloud Vision analiza imágenes y te dice qué hay en ellas: devuelve etiquetas descriptivas, lee texto impreso y manuscrito (OCR), detecta rostros, reconoce monumentos y logotipos famosos, y mucho más. sgcWebSockets Enterprise incluye un cliente gRPC de Vision tipado que se apoya sobre TsgcGRPCClient, así que puedes enviar una imagen y leer de vuelta sus anotaciones desde Delphi y C++Builder sin ningún runtime externo ni protobufs escritos a mano.

Cómo funciona

Cloud Vision expone un servicio gRPC ImageAnnotator. Una petición es un lote de una o más imágenes, cada una emparejada con la lista de funciones que quieres detectar, y la respuesta es un lote correspondiente de anotaciones. Todo el intercambio son mensajes de Protocol Buffers enmarcados sobre HTTP/2.

sgcWebSockets ya incluye una pila HTTP/2 completa, así que el transporte es un TsgcHTTP2Client apuntando a vision.googleapis.com:443 sobre TLS. TsgcGRPCClient se apoya encima y hace el enmarcado gRPC, las cabeceras, los timeouts y el análisis de trailers. Las clases de mensaje de Vision en sgcGRPC_Google_Vision son helpers de protobuf tipados: rellenas el objeto de petición, llamas a ToBytes para serializarlo, lo envías con el cliente genérico, y cargas los bytes de la respuesta de vuelta en un objeto de respuesta tipado con LoadFromBytes.

La autenticación es el flujo estándar de cuenta de servicio de Google. Un JWT autofirmado se intercambia por un bearer token y se envía como metadata gRPC en cada llamada. Como el JWT está vinculado a la audiencia, el token se dirige al endpoint de Vision para que vision.googleapis.com lo acepte.

Configurar los clientes

Crea el transporte HTTP/2, adjunta el cliente gRPC a él, y dile al canal que use el content type binario de protobuf. Vision habla application/grpc+proto.

uses
  sgcHTTP2_Client, sgcGRPC_Types, sgcGRPC_Client,
  sgcGRPC_Google_Vision;

var
  HTTP2: TsgcHTTP2Client;
  GRPC: TsgcGRPCClient;
begin
  HTTP2 := TsgcHTTP2Client.Create(nil);
  HTTP2.Host := 'vision.googleapis.com';
  HTTP2.Port := 443;
  HTTP2.TLS  := True;

  GRPC := TsgcGRPCClient.Create(nil);
  GRPC.Client := HTTP2;
  GRPC.ChannelOptions.ContentType := grpcProto;
  GRPC.ChannelOptions.Compression := grpcNoCompression;
end;

Autenticación

El bearer token proviene de tus credenciales de cuenta de servicio. Configura las opciones JWT con los valores de la clave JSON descargada, apunta la audiencia al endpoint de Vision, y añade el token resultante a DefaultMetadata para que viaje en cada llamada gRPC.

// configure the service-account JWT (values from the JSON key file)
CloudClient.GoogleCloudOptions.Authentication := gcaJWT;
CloudClient.GoogleCloudOptions.JWT.ClientEmail  := ClientEmail;
CloudClient.GoogleCloudOptions.JWT.PrivateKeyId := PrivateKeyId;
CloudClient.GoogleCloudOptions.JWT.PrivateKey.Text := PrivateKey;
CloudClient.GoogleCloudOptions.JWT.ProjectId := ProjectId;
// the self-signed JWT is audience-bound to the Vision endpoint
CloudClient.GoogleCloudOptions.JWT.API_Endpoint :=
  'https://vision.googleapis.com/';

// once the token is acquired, send it as gRPC metadata
GRPC.DefaultMetadata.Clear;
GRPC.DefaultMetadata.Add('authorization', 'Bearer ' + Token);

Anotar una imagen

Para analizar una imagen construyes un TsgcGRPCVisionBatchAnnotateImagesRequest, añades una petición de imagen, la apuntas a la imagen (aquí una URI de Google Cloud Storage, pero también puedes enviar bytes en bruto), y añades una o más funciones. Cada función tiene un FeatureType y un MaxResults opcional. Serializa con ToBytes y llama al método BatchAnnotateImages del servicio ImageAnnotator.

var
  oRequest: TsgcGRPCVisionBatchAnnotateImagesRequest;
  oImgReq: TsgcGRPCVisionAnnotateImageRequest;
  oFeature: TsgcGRPCVisionFeature;
  oResponse: TsgcGRPCResponse;
begin
  oRequest := TsgcGRPCVisionBatchAnnotateImagesRequest.Create;
  try
    oImgReq := oRequest.AddRequest;
    oImgReq.Image.Source.GcsImageUri :=
      'gs://cloud-samples-data/vision/demo-image.jpg';

    oFeature := oImgReq.AddFeature;
    oFeature.FeatureType := 4;   // LABEL_DETECTION
    oFeature.MaxResults := 10;

    oResponse := GRPC.Call('google.cloud.vision.v1.ImageAnnotator',
      'BatchAnnotateImages', oRequest.ToBytes);
  finally
    oRequest.Free;
  end;
end;

Los valores de FeatureType siguen el enum de la API de Vision: 1 FACE_DETECTION, 2 LANDMARK_DETECTION, 3 LOGO_DETECTION, 4 LABEL_DETECTION, 5 TEXT_DETECTION, 6 DOCUMENT_TEXT_DETECTION, y los demás. Para ejecutar varias detecciones sobre la misma imagen en un solo viaje de ida y vuelta, añade más de una función.

Leer las anotaciones

Los bytes de la respuesta se cargan directamente en una respuesta de lote tipada. Cada imagen del lote lleva listas separadas para las anotaciones de etiquetas, monumentos, logotipos y texto. Cada entrada es un TsgcGRPCVisionEntityAnnotation con una Description y, para las etiquetas, una puntuación de confianza Score.

var
  oResponse: TsgcGRPCVisionBatchAnnotateImagesResponse;
  oImgResp: TsgcGRPCVisionAnnotateImageResponse;
  i, j: Integer;
begin
  oResponse := TsgcGRPCVisionBatchAnnotateImagesResponse.Create;
  try
    oResponse.LoadFromBytes(aData);
    for i := 0 to oResponse.ResponseCount - 1 do
    begin
      oImgResp := oResponse.Response(i);

      for j := 0 to oImgResp.LabelAnnotationCount - 1 do
        Memo1.Lines.Add('Label: ' + oImgResp.LabelAnnotation(j).Description +
          ' (score: ' + FloatToStr(oImgResp.LabelAnnotation(j).Score) + ')');

      for j := 0 to oImgResp.TextAnnotationCount - 1 do
        Memo1.Lines.Add('Text: ' + oImgResp.TextAnnotation(j).Description);

      for j := 0 to oImgResp.LandmarkAnnotationCount - 1 do
        Memo1.Lines.Add('Landmark: ' + oImgResp.LandmarkAnnotation(j).Description);
    end;
  finally
    oResponse.Free;
  end;
end;

Imágenes desde almacenamiento o desde bytes

La fuente de la imagen es flexible. Establece Image.Source.GcsImageUri para un objeto en Google Cloud Storage, o Image.Source.ImageUri para una URL HTTP(S) pública. Para anotar un archivo local, léelo en un TBytes y asígnalo a Image.Content en su lugar, de modo que la imagen viaje en línea en la petición. Las mismas clases de petición y respuesta manejan cualquier fuente.

Síncrono o asíncrono

El ejemplo de arriba usa el Call bloqueante, que devuelve un TsgcGRPCResponse con el StatusCode, los bytes Data en bruto y los trailers. Para mantener la interfaz receptiva, usa CallAsync y maneja la respuesta en el evento OnGRPCResponse, donde analizas aResponse.Data exactamente de la misma manera. Un estado no-OK aflora a través de OnGRPCError, y un fallo de transporte a través de OnGRPCException.

Disponibilidad

El cliente gRPC de Vision tipado forma parte de la edición Enterprise de sgcWebSockets y se ejecuta en Windows, macOS, Linux, iOS y Android. Un ejemplo listo para ejecutar está en Demos\21.GRPC\13.Vision, y la referencia completa está en la página de producto del Cliente gRPC.

¿Preguntas o comentarios? Ponte en contacto. Recibirás respuesta de las personas que escribieron el código.