Elaborazione asincrona delle immagini


Questo tutorial illustra l'utilizzo di Cloud Run for Anthos, dell'API Cloud Vision e ImageMagick per rilevare e sfocare le immagini offensive caricate in un bucket Cloud Storage. Questo tutorial si basa sul tutorial Utilizzo di Pub/Sub con Cloud Run for Anthos.

Questo tutorial illustra come modificare un'app di esempio esistente. Puoi anche scaricare l'app completata se vuoi.

Obiettivi

  • Scrivi, crea ed esegui il deployment di un servizio di elaborazione dati asincrono in Cloud Run for Anthos.
  • Richiamare il servizio caricando un file in Cloud Storage, creando un messaggio Pub/Sub.
  • Usa l'API Cloud Vision per rilevare contenuti per adulti o violenti.
  • Utilizza ImageMagick per sfocare le immagini offensive.
  • Testa il servizio caricando l'immagine di uno zombie che mangia carne.

Costi

In questo documento vengono utilizzati i seguenti componenti fatturabili di Google Cloud:

Per generare una stima dei costi in base all'utilizzo previsto, utilizza il Calcolatore prezzi. I nuovi utenti di Google Cloud possono essere idonei a una prova senza costi aggiuntivi.

Prima di iniziare

  1. Accedi al tuo account Google Cloud. Se non conosci Google Cloud, crea un account per valutare le prestazioni dei nostri prodotti in scenari reali. I nuovi clienti ricevono anche 300 $di crediti gratuiti per l'esecuzione, il test e il deployment dei carichi di lavoro.
  2. Nella pagina del selettore di progetti della console Google Cloud, seleziona o crea un progetto Google Cloud.

    Vai al selettore progetti

  3. Assicurati che la fatturazione sia attivata per il tuo progetto Google Cloud.

  4. Nella pagina del selettore di progetti della console Google Cloud, seleziona o crea un progetto Google Cloud.

    Vai al selettore progetti

  5. Assicurati che la fatturazione sia attivata per il tuo progetto Google Cloud.

  6. Abilita le API Cloud Run for Anthos and Cloud Vision.

    Abilita le API

  7. Installa e inizializza l'interfaccia alla gcloud CLI.
  8. Installa il componente kubectl:
    gcloud components install kubectl
  9. Aggiorna i componenti:
    gcloud components update
  10. Utilizzando Cloud Run for Anthos, crea un nuovo cluster utilizzando le istruzioni in Configurazione di Cloud Run for Anthos.
  11. Configura un argomento Pub/Sub, una sottoscrizione push sicura e un servizio Cloud Run for Anthos iniziale per gestire i messaggi seguendo il tutorial Utilizzo di Pub/Sub con Cloud Run for Anthos.

Configurazione dei valori predefiniti gcloud

Per configurare gcloud con i valori predefiniti per il servizio Cloud Run for Anthos:

  1. Imposta il progetto predefinito:

    gcloud config set project PROJECT_ID

    Sostituisci PROJECT_ID con il nome del progetto che utilizzi per questo tutorial.

  2. Configura gcloud per il tuo cluster:

    gcloud config set run/platform gke
    gcloud config set run/cluster CLUSTER-NAME
    gcloud config set run/cluster_location REGION

    Sostituisci:

    • CLUSTER-NAME con il nome utilizzato per il cluster,
    • REGION con la località del cluster supportata di tua scelta.

Informazioni sulla sequenza delle operazioni

Il flusso di dati in questo tutorial è il seguente:

  1. Un utente carica un'immagine in un bucket Cloud Storage.
  2. Cloud Storage pubblica un messaggio sul nuovo file in Pub/Sub.
  3. Pub/Sub esegue il push del messaggio al servizio Cloud Run for Anthos.
  4. Il servizio Cloud Run for Anthos recupera il file immagine a cui si fa riferimento nel messaggio Pub/Sub.
  5. Il servizio Cloud Run for Anthos utilizza l'API Cloud Vision per analizzare l'immagine.
  6. Se vengono rilevati contenuti violenti o per adulti, il servizio Cloud Run for Anthos utilizza ImageMagick per sfocare l'immagine.
  7. Il servizio Cloud Run for Anthos carica l'immagine sfocata in un altro bucket Cloud Storage da utilizzare.

Il successivo utilizzo dell'immagine sfocata viene lasciato come esercizio per il lettore.

Configurazione dei bucket Cloud Storage

  1. Crea un bucket Cloud Storage per il caricamento delle immagini, dove INPUT_BUCKET_NAME è un nome di bucket univoco a livello globale:

    gsutil mb gs://INPUT_BUCKET_NAME

    Il servizio Cloud Run for Anthos legge solo questo bucket.

  2. Crea un secondo bucket Cloud Storage per ricevere immagini sfocate, in cui BLURRED_BUCKET_NAME è il nome di un bucket univoco a livello globale:

    gsutil mb gs://BLURRED_BUCKET_NAME

    Il servizio Cloud Run for Anthos carica le immagini sfocate in questo bucket. L'utilizzo di un bucket separato impedisce alle immagini elaborate di riattivare il servizio.

Nei passaggi seguenti, creerai ed eseguirai il deployment di un servizio che elabora la notifica dei caricamenti di file su INPUT_BUCKET_NAME. Puoi attivare il recapito delle notifiche dopo aver eseguito il deployment e il test del servizio, per evitare chiamate premature al nuovo servizio.

Modifica del codice campione del tutorial Pub/Sub

Questo tutorial si basa sul codice assemblato nel tutorial sull'utilizzo di Pub/Sub. Se non hai ancora completato il tutorial, fallo ora, saltando i passaggi di pulizia e torna qui per aggiungere il comportamento di elaborazione delle immagini.

Aggiunta del codice di elaborazione dell'immagine in corso...

Il codice di elaborazione dell'immagine è separato dalla gestione delle richieste per favorire la leggibilità e la facilità di test. Per aggiungere il codice per l'elaborazione delle immagini:

  1. Cambia la directory del codice campione del tutorial su Pub/Sub.

  2. Aggiungi il codice per importare le dipendenze di elaborazione delle immagini, incluse le librerie da integrare con i servizi Google Cloud, ImageMagick e il file system.

    Node.js

    Apri un nuovo file image.js nell'editor e copia quanto segue:
    const gm = require('gm').subClass({imageMagick: true});
    const fs = require('fs');
    const {promisify} = require('util');
    const path = require('path');
    const vision = require('@google-cloud/vision');
    
    const {Storage} = require('@google-cloud/storage');
    const storage = new Storage();
    const client = new vision.ImageAnnotatorClient();
    
    const {BLURRED_BUCKET_NAME} = process.env;

    Python

    Apri un nuovo file image.py nell'editor e copia quanto segue:
    import os
    import tempfile
    
    from google.cloud import storage, vision
    from wand.image import Image
    
    storage_client = storage.Client()
    vision_client = vision.ImageAnnotatorClient()

    Go

    Apri un nuovo file imagemagick/imagemagick.go nell'editor e copia quanto segue:
    
    // Package imagemagick contains an example of using ImageMagick to process a
    // file uploaded to Cloud Storage.
    package imagemagick
    
    import (
    	"context"
    	"errors"
    	"fmt"
    	"log"
    	"os"
    	"os/exec"
    
    	"cloud.google.com/go/storage"
    	vision "cloud.google.com/go/vision/apiv1"
    	"cloud.google.com/go/vision/v2/apiv1/visionpb"
    )
    
    // Global API clients used across function invocations.
    var (
    	storageClient *storage.Client
    	visionClient  *vision.ImageAnnotatorClient
    )
    
    func init() {
    	// Declare a separate err variable to avoid shadowing the client variables.
    	var err error
    
    	storageClient, err = storage.NewClient(context.Background())
    	if err != nil {
    		log.Fatalf("storage.NewClient: %v", err)
    	}
    
    	visionClient, err = vision.NewImageAnnotatorClient(context.Background())
    	if err != nil {
    		log.Fatalf("vision.NewAnnotatorClient: %v", err)
    	}
    }
    

    Java

    Apri un nuovo file src/main/java/com/example/cloudrun/ImageMagick.java nell'editor e copia quanto segue:
    import com.google.cloud.storage.Blob;
    import com.google.cloud.storage.BlobId;
    import com.google.cloud.storage.BlobInfo;
    import com.google.cloud.storage.Storage;
    import com.google.cloud.storage.StorageOptions;
    import com.google.cloud.vision.v1.AnnotateImageRequest;
    import com.google.cloud.vision.v1.AnnotateImageResponse;
    import com.google.cloud.vision.v1.BatchAnnotateImagesResponse;
    import com.google.cloud.vision.v1.Feature;
    import com.google.cloud.vision.v1.Feature.Type;
    import com.google.cloud.vision.v1.Image;
    import com.google.cloud.vision.v1.ImageAnnotatorClient;
    import com.google.cloud.vision.v1.ImageSource;
    import com.google.cloud.vision.v1.SafeSearchAnnotation;
    import com.google.gson.JsonObject;
    import java.io.IOException;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import java.util.ArrayList;
    import java.util.List;
    
    public class ImageMagick {
    
      private static final String BLURRED_BUCKET_NAME = System.getenv("BLURRED_BUCKET_NAME");
      private static Storage storage = StorageOptions.getDefaultInstance().getService();

  3. Aggiungi il codice per ricevere un messaggio Pub/Sub come oggetto evento e controlla l'elaborazione delle immagini.

    L'evento contiene dati relativi all'immagine caricata originariamente. Questo codice determina se l'immagine deve essere sfocata controllando i risultati di un'analisi Cloud Vision per i contenuti violenti o per adulti.

    Node.js

    // Blurs uploaded images that are flagged as Adult or Violence.
    exports.blurOffensiveImages = async event => {
      // This event represents the triggering Cloud Storage object.
      const object = event;
    
      const file = storage.bucket(object.bucket).file(object.name);
      const filePath = `gs://${object.bucket}/${object.name}`;
    
      console.log(`Analyzing ${file.name}.`);
    
      try {
        const [result] = await client.safeSearchDetection(filePath);
        const detections = result.safeSearchAnnotation || {};
    
        if (
          // Levels are defined in https://cloud.google.com/vision/docs/reference/rest/v1/AnnotateImageResponse#likelihood
          detections.adult === 'VERY_LIKELY' ||
          detections.violence === 'VERY_LIKELY'
        ) {
          console.log(`Detected ${file.name} as inappropriate.`);
          return blurImage(file, BLURRED_BUCKET_NAME);
        } else {
          console.log(`Detected ${file.name} as OK.`);
        }
      } catch (err) {
        console.error(`Failed to analyze ${file.name}.`, err);
        throw err;
      }
    };

    Python

    def blur_offensive_images(data):
        """Blurs uploaded images that are flagged as Adult or Violence.
    
        Args:
            data: Pub/Sub message data
        """
        file_data = data
    
        file_name = file_data["name"]
        bucket_name = file_data["bucket"]
    
        blob = storage_client.bucket(bucket_name).get_blob(file_name)
        blob_uri = f"gs://{bucket_name}/{file_name}"
        blob_source = vision.Image(source=vision.ImageSource(image_uri=blob_uri))
    
        # Ignore already-blurred files
        if file_name.startswith("blurred-"):
            print(f"The image {file_name} is already blurred.")
            return
    
        print(f"Analyzing {file_name}.")
    
        result = vision_client.safe_search_detection(image=blob_source)
        detected = result.safe_search_annotation
    
        # Process image
        if detected.adult == 5 or detected.violence == 5:
            print(f"The image {file_name} was detected as inappropriate.")
            return __blur_image(blob)
        else:
            print(f"The image {file_name} was detected as OK.")
    
    

    Go

    
    // GCSEvent is the payload of a GCS event.
    type GCSEvent struct {
    	Bucket string `json:"bucket"`
    	Name   string `json:"name"`
    }
    
    // BlurOffensiveImages blurs offensive images uploaded to GCS.
    func BlurOffensiveImages(ctx context.Context, e GCSEvent) error {
    	outputBucket := os.Getenv("BLURRED_BUCKET_NAME")
    	if outputBucket == "" {
    		return errors.New("BLURRED_BUCKET_NAME must be set")
    	}
    
    	img := vision.NewImageFromURI(fmt.Sprintf("gs://%s/%s", e.Bucket, e.Name))
    
    	resp, err := visionClient.DetectSafeSearch(ctx, img, nil)
    	if err != nil {
    		return fmt.Errorf("AnnotateImage: %w", err)
    	}
    
    	if resp.GetAdult() == visionpb.Likelihood_VERY_LIKELY ||
    		resp.GetViolence() == visionpb.Likelihood_VERY_LIKELY {
    		return blur(ctx, e.Bucket, outputBucket, e.Name)
    	}
    	log.Printf("The image %q was detected as OK.", e.Name)
    	return nil
    }
    

    Java

    // Blurs uploaded images that are flagged as Adult or Violence.
    public static void blurOffensiveImages(JsonObject data) {
      String fileName = data.get("name").getAsString();
      String bucketName = data.get("bucket").getAsString();
      BlobInfo blobInfo = BlobInfo.newBuilder(bucketName, fileName).build();
      // Construct URI to GCS bucket and file.
      String gcsPath = String.format("gs://%s/%s", bucketName, fileName);
      System.out.println(String.format("Analyzing %s", fileName));
    
      // Construct request.
      List<AnnotateImageRequest> requests = new ArrayList<>();
      ImageSource imgSource = ImageSource.newBuilder().setImageUri(gcsPath).build();
      Image img = Image.newBuilder().setSource(imgSource).build();
      Feature feature = Feature.newBuilder().setType(Type.SAFE_SEARCH_DETECTION).build();
      AnnotateImageRequest request =
          AnnotateImageRequest.newBuilder().addFeatures(feature).setImage(img).build();
      requests.add(request);
    
      // Send request to the Vision API.
      try (ImageAnnotatorClient client = ImageAnnotatorClient.create()) {
        BatchAnnotateImagesResponse response = client.batchAnnotateImages(requests);
        List<AnnotateImageResponse> responses = response.getResponsesList();
        for (AnnotateImageResponse res : responses) {
          if (res.hasError()) {
            System.out.println(String.format("Error: %s\n", res.getError().getMessage()));
            return;
          }
          // Get Safe Search Annotations
          SafeSearchAnnotation annotation = res.getSafeSearchAnnotation();
          if (annotation.getAdultValue() == 5 || annotation.getViolenceValue() == 5) {
            System.out.println(String.format("Detected %s as inappropriate.", fileName));
            blur(blobInfo);
          } else {
            System.out.println(String.format("Detected %s as OK.", fileName));
          }
        }
      } catch (Exception e) {
        System.out.println(String.format("Error with Vision API: %s", e.getMessage()));
      }
    }

  4. Recupera l'immagine di riferimento dal bucket di input Cloud Storage creato sopra, utilizza ImageMagick per trasformare l'immagine con un effetto di sfocatura e carica il risultato nel bucket di output.

    Node.js

    // Blurs the given file using ImageMagick, and uploads it to another bucket.
    const blurImage = async (file, blurredBucketName) => {
      const tempLocalPath = `/tmp/${path.parse(file.name).base}`;
    
      // Download file from bucket.
      try {
        await file.download({destination: tempLocalPath});
    
        console.log(`Downloaded ${file.name} to ${tempLocalPath}.`);
      } catch (err) {
        throw new Error(`File download failed: ${err}`);
      }
    
      await new Promise((resolve, reject) => {
        gm(tempLocalPath)
          .blur(0, 16)
          .write(tempLocalPath, (err, stdout) => {
            if (err) {
              console.error('Failed to blur image.', err);
              reject(err);
            } else {
              console.log(`Blurred image: ${file.name}`);
              resolve(stdout);
            }
          });
      });
    
      // Upload result to a different bucket, to avoid re-triggering this function.
      const blurredBucket = storage.bucket(blurredBucketName);
    
      // Upload the Blurred image back into the bucket.
      const gcsPath = `gs://${blurredBucketName}/${file.name}`;
      try {
        await blurredBucket.upload(tempLocalPath, {destination: file.name});
        console.log(`Uploaded blurred image to: ${gcsPath}`);
      } catch (err) {
        throw new Error(`Unable to upload blurred image to ${gcsPath}: ${err}`);
      }
    
      // Delete the temporary file.
      const unlink = promisify(fs.unlink);
      return unlink(tempLocalPath);
    };

    Python

    def __blur_image(current_blob):
        """Blurs the given file using ImageMagick.
    
        Args:
            current_blob: a Cloud Storage blob
        """
        file_name = current_blob.name
        _, temp_local_filename = tempfile.mkstemp()
    
        # Download file from bucket.
        current_blob.download_to_filename(temp_local_filename)
        print(f"Image {file_name} was downloaded to {temp_local_filename}.")
    
        # Blur the image using ImageMagick.
        with Image(filename=temp_local_filename) as image:
            image.resize(*image.size, blur=16, filter="hamming")
            image.save(filename=temp_local_filename)
    
        print(f"Image {file_name} was blurred.")
    
        # Upload result to a second bucket, to avoid re-triggering the function.
        # You could instead re-upload it to the same bucket + tell your function
        # to ignore files marked as blurred (e.g. those with a "blurred" prefix)
        blur_bucket_name = os.getenv("BLURRED_BUCKET_NAME")
        blur_bucket = storage_client.bucket(blur_bucket_name)
        new_blob = blur_bucket.blob(file_name)
        new_blob.upload_from_filename(temp_local_filename)
        print(f"Blurred image uploaded to: gs://{blur_bucket_name}/{file_name}")
    
        # Delete the temporary file.
        os.remove(temp_local_filename)
    
    

    Go

    
    // blur blurs the image stored at gs://inputBucket/name and stores the result in
    // gs://outputBucket/name.
    func blur(ctx context.Context, inputBucket, outputBucket, name string) error {
    	inputBlob := storageClient.Bucket(inputBucket).Object(name)
    	r, err := inputBlob.NewReader(ctx)
    	if err != nil {
    		return fmt.Errorf("NewReader: %w", err)
    	}
    
    	outputBlob := storageClient.Bucket(outputBucket).Object(name)
    	w := outputBlob.NewWriter(ctx)
    	defer w.Close()
    
    	// Use - as input and output to use stdin and stdout.
    	cmd := exec.Command("convert", "-", "-blur", "0x8", "-")
    	cmd.Stdin = r
    	cmd.Stdout = w
    
    	if err := cmd.Run(); err != nil {
    		return fmt.Errorf("cmd.Run: %w", err)
    	}
    
    	log.Printf("Blurred image uploaded to gs://%s/%s", outputBlob.BucketName(), outputBlob.ObjectName())
    
    	return nil
    }
    

    Java

      // Blurs the file described by blobInfo using ImageMagick,
      // and uploads it to the blurred bucket.
      public static void blur(BlobInfo blobInfo) throws IOException {
        String bucketName = blobInfo.getBucket();
        String fileName = blobInfo.getName();
        // Download image
        Blob blob = storage.get(BlobId.of(bucketName, fileName));
        Path download = Paths.get("/tmp/", fileName);
        blob.downloadTo(download);
    
        // Construct the command.
        List<String> args = new ArrayList<>();
        args.add("convert");
        args.add(download.toString());
        args.add("-blur");
        args.add("0x8");
        Path upload = Paths.get("/tmp/", "blurred-" + fileName);
        args.add(upload.toString());
        try {
          ProcessBuilder pb = new ProcessBuilder(args);
          Process process = pb.start();
          process.waitFor();
        } catch (Exception e) {
          System.out.println(String.format("Error: %s", e.getMessage()));
        }
    
        // Upload image to blurred bucket.
        BlobId blurredBlobId = BlobId.of(BLURRED_BUCKET_NAME, fileName);
        BlobInfo blurredBlobInfo =
            BlobInfo.newBuilder(blurredBlobId).setContentType(blob.getContentType()).build();
        try {
          byte[] blurredFile = Files.readAllBytes(upload);
          Blob blurredBlob = storage.create(blurredBlobInfo, blurredFile);
          System.out.println(
              String.format("Blurred image uploaded to: gs://%s/%s", BLURRED_BUCKET_NAME, fileName));
        } catch (Exception e) {
          System.out.println(String.format("Error in upload: %s", e.getMessage()));
        }
    
        // Remove images from fileSystem
        Files.delete(download);
        Files.delete(upload);
      }
    }

Integrazione dell'elaborazione delle immagini nel codice campione Pub/Sub

Per modificare il servizio esistente e incorporare il codice di elaborazione dell'immagine:

  1. Aggiungi nuove dipendenze per il servizio, incluse le librerie client di Cloud Vision e Cloud Storage:

    Node.js

    npm install --save gm @google-cloud/storage @google-cloud/vision

    Python

    Aggiungi le librerie client necessarie in modo che requirements.txt abbia il seguente aspetto:
    Flask==2.1.0
    pytest==7.0.1; python_version > "3.0"
    # pin pytest to 4.6.11 for Python2.
    pytest==7.0.1; python_version < "3.0"
    gunicorn==20.1.0
    google-cloud-vision==3.4.2
    google-cloud-storage==2.9.0
    Wand==0.6.11
    

    Go

    L'applicazione di esempio go utilizza i moduli go, le nuove dipendenze aggiunte sopra nell'istruzione di importazione imagemagick/imagemagick.go verranno scaricate automaticamente dal comando successivo che ne richiede.

    Java

    Aggiungi la seguente dipendenza in <dependencyManagement> in pom.xml:
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>spring-cloud-gcp-dependencies</artifactId>
      <version>4.5.1</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
    
    Aggiungi le seguenti dipendenze in <dependencies> in pom.xml:
    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>spring-cloud-gcp-starter-vision</artifactId>
    </dependency>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>spring-cloud-gcp-starter-storage</artifactId>
    </dependency>
    

  2. Aggiungi il pacchetto di sistema ImageMagick al tuo container modificando l'istruzione Dockerfile sotto l'istruzione FROM. Se utilizzi un Dockerfile "multi-stage", inseriscilo nella fase finale.

    Debian/Ubuntu
    
    # Install Imagemagick into the container image.
    # For more on system packages review the system packages tutorial.
    # https://cloud.google.com/run/docs/tutorials/system-packages#dockerfile
    RUN set -ex; \
      apt-get -y update; \
      apt-get -y install imagemagick; \
      rm -rf /var/lib/apt/lists/*
    
    Alpine
    
    # Install Imagemagick into the container image.
    # For more on system packages review the system packages tutorial.
    # https://cloud.google.com/run/docs/tutorials/system-packages#dockerfile
    RUN apk add --no-cache imagemagick
    

    Scopri di più sull'utilizzo dei pacchetti di sistema nel servizio Cloud Run for Anthos nel tutorial sull'utilizzo dei pacchetti di sistema.

  3. Sostituisci il codice di gestione dei messaggi Pub/Sub esistente con una chiamata funzione alla nostra nuova logica di sfocatura.

    Node.js

    Il file app.js definisce l'app Express.js e prepara i messaggi Pub/Sub ricevuti per l'uso. Apporta le seguenti modifiche:

    • Aggiungi codice per importare il nuovo file image.js
    • Rimuovi il codice "Hello World" esistente dalla route
    • Aggiungi codice per convalidare ulteriormente il messaggio Pub/Sub
    • Aggiungi codice per chiamare la nuova funzione di elaborazione delle immagini

      Al termine, il codice sarà simile a questo:

    
    const express = require('express');
    const app = express();
    
    // This middleware is available in Express v4.16.0 onwards
    app.use(express.json());
    
    const image = require('./image');
    
    app.post('/', async (req, res) => {
      if (!req.body) {
        const msg = 'no Pub/Sub message received';
        console.error(`error: ${msg}`);
        res.status(400).send(`Bad Request: ${msg}`);
        return;
      }
      if (!req.body.message || !req.body.message.data) {
        const msg = 'invalid Pub/Sub message format';
        console.error(`error: ${msg}`);
        res.status(400).send(`Bad Request: ${msg}`);
        return;
      }
    
      // Decode the Pub/Sub message.
      const pubSubMessage = req.body.message;
      let data;
      try {
        data = Buffer.from(pubSubMessage.data, 'base64').toString().trim();
        data = JSON.parse(data);
      } catch (err) {
        const msg =
          'Invalid Pub/Sub message: data property is not valid base64 encoded JSON';
        console.error(`error: ${msg}: ${err}`);
        res.status(400).send(`Bad Request: ${msg}`);
        return;
      }
    
      // Validate the message is a Cloud Storage event.
      if (!data.name || !data.bucket) {
        const msg =
          'invalid Cloud Storage notification: expected name and bucket properties';
        console.error(`error: ${msg}`);
        res.status(400).send(`Bad Request: ${msg}`);
        return;
      }
    
      try {
        await image.blurOffensiveImages(data);
        res.status(204).send();
      } catch (err) {
        console.error(`error: Blurring image: ${err}`);
        res.status(500).send();
      }
    });

    Python

    Il file main.py definisce l'app Flask e prepara l'utilizzo dei messaggi Pub/Sub ricevuti. Apporta le seguenti modifiche:

    • Aggiungi codice per importare il nuovo file image.py
    • Rimuovi il codice "Hello World" esistente dalla route
    • Aggiungi codice per convalidare ulteriormente il messaggio Pub/Sub
    • Aggiungi codice per chiamare la nuova funzione di elaborazione delle immagini

      Al termine, il codice sarà simile a questo:

    import base64
    import json
    import os
    
    from flask import Flask, request
    
    import image
    
    app = Flask(__name__)
    
    @app.route("/", methods=["POST"])
    def index():
        """Receive and parse Pub/Sub messages containing Cloud Storage event data."""
        envelope = request.get_json()
        if not envelope:
            msg = "no Pub/Sub message received"
            print(f"error: {msg}")
            return f"Bad Request: {msg}", 400
    
        if not isinstance(envelope, dict) or "message" not in envelope:
            msg = "invalid Pub/Sub message format"
            print(f"error: {msg}")
            return f"Bad Request: {msg}", 400
    
        # Decode the Pub/Sub message.
        pubsub_message = envelope["message"]
    
        if isinstance(pubsub_message, dict) and "data" in pubsub_message:
            try:
                data = json.loads(base64.b64decode(pubsub_message["data"]).decode())
    
            except Exception as e:
                msg = (
                    "Invalid Pub/Sub message: "
                    "data property is not valid base64 encoded JSON"
                )
                print(f"error: {e}")
                return f"Bad Request: {msg}", 400
    
            # Validate the message is a Cloud Storage event.
            if not data["name"] or not data["bucket"]:
                msg = (
                    "Invalid Cloud Storage notification: "
                    "expected name and bucket properties"
                )
                print(f"error: {msg}")
                return f"Bad Request: {msg}", 400
    
            try:
                image.blur_offensive_images(data)
                return ("", 204)
    
            except Exception as e:
                print(f"error: {e}")
                return ("", 500)
    
        return ("", 500)
    

    Go

    Il file main.go definisce il servizio HTTP e prepara i messaggi Pub/Sub ricevuti per l'uso. Apporta le seguenti modifiche:

    • Aggiungi codice per importare il nuovo file imagemagick.go
    • Rimuovi il codice "Hello World" esistente dal gestore
    • Aggiungi codice per convalidare ulteriormente il messaggio Pub/Sub
    • Aggiungi codice per chiamare la nuova funzione di elaborazione delle immagini

    
    // Sample image-processing is a Cloud Run service which performs asynchronous processing on images.
    package main
    
    import (
    	"encoding/json"
    	"io/ioutil"
    	"log"
    	"net/http"
    	"os"
    
    	"github.com/GoogleCloudPlatform/golang-samples/run/image-processing/imagemagick"
    )
    
    func main() {
    	http.HandleFunc("/", HelloPubSub)
    	// Determine port for HTTP service.
    	port := os.Getenv("PORT")
    	if port == "" {
    		port = "8080"
    	}
    	// Start HTTP server.
    	log.Printf("Listening on port %s", port)
    	if err := http.ListenAndServe(":"+port, nil); err != nil {
    		log.Fatal(err)
    	}
    }
    
    // PubSubMessage is the payload of a Pub/Sub event.
    // See the documentation for more details:
    // https://cloud.google.com/pubsub/docs/reference/rest/v1/PubsubMessage
    type PubSubMessage struct {
    	Message struct {
    		Data []byte `json:"data,omitempty"`
    		ID   string `json:"id"`
    	} `json:"message"`
    	Subscription string `json:"subscription"`
    }
    
    // HelloPubSub receives and processes a Pub/Sub push message.
    func HelloPubSub(w http.ResponseWriter, r *http.Request) {
    	var m PubSubMessage
    	body, err := ioutil.ReadAll(r.Body)
    	if err != nil {
    		log.Printf("ioutil.ReadAll: %v", err)
    		http.Error(w, "Bad Request", http.StatusBadRequest)
    		return
    	}
    	if err := json.Unmarshal(body, &m); err != nil {
    		log.Printf("json.Unmarshal: %v", err)
    		http.Error(w, "Bad Request", http.StatusBadRequest)
    		return
    	}
    
    	var e imagemagick.GCSEvent
    	if err := json.Unmarshal(m.Message.Data, &e); err != nil {
    		log.Printf("json.Unmarshal: %v", err)
    		http.Error(w, "Bad Request", http.StatusBadRequest)
    		return
    	}
    
    	if e.Name == "" || e.Bucket == "" {
    		log.Printf("invalid GCSEvent: expected name and bucket")
    		http.Error(w, "Bad Request", http.StatusBadRequest)
    		return
    	}
    
    	if err := imagemagick.BlurOffensiveImages(r.Context(), e); err != nil {
    		log.Printf("imagemagick.BlurOffensiveImages: %v", err)
    		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
    	}
    }
    

    Java

    Il file PubSubController.java definisce il controller che gestisce le richieste HTTP e prepara l'utilizzo dei messaggi Pub/Sub ricevuti. Apporta le seguenti modifiche:

    • Aggiungere le nuove importazioni
    • Rimuovi il codice "Hello World" esistente dal controller
    • Aggiungi codice per convalidare ulteriormente il messaggio Pub/Sub
    • Aggiungi codice per chiamare la nuova funzione di elaborazione delle immagini

    import com.google.gson.JsonObject;
    import com.google.gson.JsonParser;
    import java.util.Base64;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    // PubsubController consumes a Pub/Sub message.
    @RestController
    public class PubSubController {
      @RequestMapping(value = "/", method = RequestMethod.POST)
      public ResponseEntity<String> receiveMessage(@RequestBody Body body) {
        // Get PubSub message from request body.
        Body.Message message = body.getMessage();
        if (message == null) {
          String msg = "Bad Request: invalid Pub/Sub message format";
          System.out.println(msg);
          return new ResponseEntity<>(msg, HttpStatus.BAD_REQUEST);
        }
    
        // Decode the Pub/Sub message.
        String pubSubMessage = message.getData();
        JsonObject data;
        try {
          String decodedMessage = new String(Base64.getDecoder().decode(pubSubMessage));
          data = JsonParser.parseString(decodedMessage).getAsJsonObject();
        } catch (Exception e) {
          String msg = "Error: Invalid Pub/Sub message: data property is not valid base64 encoded JSON";
          System.out.println(msg);
          return new ResponseEntity<>(msg, HttpStatus.BAD_REQUEST);
        }
    
        // Validate the message is a Cloud Storage event.
        if (data.get("name") == null || data.get("bucket") == null) {
          String msg = "Error: Invalid Cloud Storage notification: expected name and bucket properties";
          System.out.println(msg);
          return new ResponseEntity<>(msg, HttpStatus.BAD_REQUEST);
        }
    
        try {
          ImageMagick.blurOffensiveImages(data);
        } catch (Exception e) {
          String msg = String.format("Error: Blurring image: %s", e.getMessage());
          System.out.println(msg);
          return new ResponseEntity<>(msg, HttpStatus.INTERNAL_SERVER_ERROR);
        }
        return new ResponseEntity<>(HttpStatus.OK);
      }
    }

Download dell'anteprima completa

Per recuperare l'esempio di codice completo di elaborazione delle immagini da utilizzare:

  1. Clona il repository dell'app di esempio nella macchina locale:

    Node.js

    git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git

    In alternativa, puoi scaricare l'esempio come file ZIP ed estrarlo.

    Python

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git

    In alternativa, puoi scaricare l'esempio come file ZIP ed estrarlo.

    Go

    git clone https://github.com/GoogleCloudPlatform/golang-samples.git

    In alternativa, puoi scaricare l'esempio come file ZIP ed estrarlo.

    Java

    git clone https://github.com/GoogleCloudPlatform/java-docs-samples.git

    In alternativa, puoi scaricare l'esempio come file ZIP ed estrarlo.

  2. Passa alla directory che contiene il codice campione di Cloud Run for Anthos:

    Node.js

    cd nodejs-docs-samples/run/image-processing/

    Python

    cd python-docs-samples/run/image-processing/

    Go

    cd golang-samples/run/image-processing/

    Java

    cd java-docs-samples/run/image-processing/

Spedizione del codice

Il codice di spedizione è composto da tre passaggi: la creazione di un'immagine container con Cloud Build, il caricamento dell'immagine container in Container Registry e il deployment dell'immagine container su Cloud Run for Anthos.

Per spedire il codice:

  1. Crea il container e pubblicalo su Container Registry:

    Node.js

    gcloud builds submit --tag gcr.io/PROJECT_ID/pubsub

    Dove PROJECT_ID è l'ID progetto GCP e pubsub è il nome che vuoi assegnare al servizio.

    Se l'operazione va a buon fine, verrà visualizzato un messaggio di operazione riuscita contenente l'ID, l'ora di creazione e il nome dell'immagine. L'immagine è archiviata in Container Registry e, se necessario, può essere riutilizzata.

    Python

    gcloud builds submit --tag gcr.io/PROJECT_ID/pubsub

    Dove PROJECT_ID è l'ID progetto GCP e pubsub è il nome che vuoi assegnare al servizio.

    Se l'operazione va a buon fine, verrà visualizzato un messaggio di operazione riuscita contenente l'ID, l'ora di creazione e il nome dell'immagine. L'immagine è archiviata in Container Registry e, se necessario, può essere riutilizzata.

    Go

    gcloud builds submit --tag gcr.io/PROJECT_ID/pubsub

    Dove PROJECT_ID è l'ID progetto GCP e pubsub è il nome che vuoi assegnare al servizio.

    Se l'operazione va a buon fine, verrà visualizzato un messaggio di operazione riuscita contenente l'ID, l'ora di creazione e il nome dell'immagine. L'immagine è archiviata in Container Registry e, se necessario, può essere riutilizzata.

    Java

    Questo esempio utilizza Jib per creare immagini Docker utilizzando strumenti Java comuni. Jib ottimizza le build dei container senza bisogno di un Dockerfile o senza installare Docker. Scopri di più sulla creazione di container Java con Jib.

    1. Utilizzando il Dockerfile, configura e crea un'immagine di base con i pacchetti di sistema installati per eseguire l'override dell'immagine di base predefinita di Jib:

      # Use eclipse-temurin for base image.
      # It's important to use JDK 8u191 or above that has container support enabled.
      # https://hub.docker.com/_/eclipse-temurin/
      # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
      FROM eclipse-temurin:17.0.8_7-jre
      
      # Install Imagemagick into the container image.
      # For more on system packages review the system packages tutorial.
      # https://cloud.google.com/run/docs/tutorials/system-packages#dockerfile
      RUN set -ex; \
        apt-get -y update; \
        apt-get -y install imagemagick; \
        rm -rf /var/lib/apt/lists/*

      gcloud builds submit --tag gcr.io/PROJECT_ID/imagemagick

      Dove PROJECT_ID è il tuo ID progetto GCP.

    2. Crea il tuo container finale con Jib e pubblicalo su Container Registry:

      <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>jib-maven-plugin</artifactId>
        <version>3.3.2</version>
        <configuration>
          <from>
            <image>gcr.io/PROJECT_ID/imagemagick</image>
          </from>
          <to>
            <image>gcr.io/PROJECT_ID/pubsub</image>
          </to>
        </configuration>
      </plugin>
      
      mvn compile jib:build \
        -Dimage=gcr.io/PROJECT_ID/pubsub \
        -Djib.from.image=gcr.io/PROJECT_ID/imagemagick

      Dove PROJECT_ID è il tuo ID progetto GCP.

  2. Esegui questo comando per eseguire il deployment del tuo servizio utilizzando lo stesso nome di servizio utilizzato nel tutorial Pub/Sub:

    Node.js

    gcloud run deploy pubsub-tutorial --image gcr.io/PROJECT_ID/pubsub --set-env-vars=BLURRED_BUCKET_NAME=BLURRED_BUCKET_NAME

    Python

    gcloud run deploy pubsub-tutorial --image gcr.io/PROJECT_ID/pubsub --set-env-vars=BLURRED_BUCKET_NAME=BLURRED_BUCKET_NAME

    Go

    gcloud run deploy pubsub-tutorial --image gcr.io/PROJECT_ID/pubsub --set-env-vars=BLURRED_BUCKET_NAME=BLURRED_BUCKET_NAME

    Java

    gcloud run deploy pubsub-tutorial --image gcr.io/PROJECT_ID/pubsub --set-env-vars=BLURRED_BUCKET_NAME=BLURRED_BUCKET_NAME --memory 512M

    Sostituisci PROJECT_ID con l'ID progetto GCP. pubsub è il nome del container e pubsub-tutorial è il nome del servizio. Tieni presente che viene eseguito il deployment dell'immagine container sul servizio e sul cluster configurati in precedenza in Configurazione delle impostazioni predefinite di gcloud.

    Sostituisci BLURRED_BUCKET_NAME con il bucket Cloud Storage creato in precedenza per ricevere immagini sfocate in modo da impostare la variabile di ambiente.

    Attendi il completamento del deployment: questa operazione può richiedere circa mezzo minuto. Se l'operazione riesce, la riga di comando visualizza l'URL del servizio.

Attivazione delle notifiche da Cloud Storage

Configurare Cloud Storage per pubblicare un messaggio in un argomento Pub/Sub ogni volta che un file (noto come un oggetto) viene caricato o modificato. Invia la notifica all'argomento creato in precedenza in modo che qualsiasi nuovo caricamento di file richiami il servizio.

gsutil notification create -t myRunTopic -f json gs://INPUT_BUCKET_NAME

Il comando gsutil è installato come parte di Google Cloud CLI. myRunTopic è l'argomento creato nel tutorial precedente.

Sostituisci INPUT_BUCKET_NAME con il nome utilizzato durante la creazione dei bucket.

Per ulteriori dettagli sulle notifiche di bucket di archiviazione, consulta la pagina Notifiche di modifica degli oggetti.

Prova in corso

  1. Carica un'immagine offensiva, ad esempio l'immagine di uno zombi che mangia carne:

    gsutil cp zombie.jpg gs://INPUT_BUCKET_NAME

    dove INPUT_BUCKET_NAME è il bucket Cloud Storage che hai creato in precedenza per il caricamento delle immagini.

  2. Accedi ai log del servizio:

    1. Vai alla pagina Cloud Run for Anthos nella console Google Cloud:

      Vai a Cloud Run for Anthos

    2. Fai clic sul servizio pubsub-tutorial.

    3. Seleziona la scheda Log. La visualizzazione dei log potrebbe richiedere alcuni istanti. Se non li vedi immediatamente, ricontrolla dopo qualche istante.

  3. Cerca il messaggio Blurred image: zombie.png.

  4. Puoi visualizzare le immagini sfocate nel bucket Cloud Storage BLURRED_BUCKET_NAME che hai creato in precedenza: individua il bucket nella pagina Cloud Storage della console Google Cloud

Esegui la pulizia

Se hai creato un nuovo progetto per questo tutorial, elimina il progetto. Se hai utilizzato un progetto esistente e vuoi mantenerlo senza le modifiche aggiunte in questo tutorial, elimina le risorse create per il tutorial.

Elimina il progetto

Il modo più semplice per eliminare la fatturazione è eliminare il progetto che hai creato per il tutorial.

Per eliminare il progetto:

  1. Nella console Google Cloud, vai alla pagina Gestisci risorse.

    Vai a Gestisci risorse

  2. Nell'elenco dei progetti, seleziona il progetto che vuoi eliminare, quindi fai clic su Elimina.
  3. Nella finestra di dialogo, digita l'ID del progetto e fai clic su Chiudi per eliminare il progetto.

Eliminazione delle risorse del tutorial

  1. Elimina il servizio Cloud Run for Anthos di cui hai eseguito il deployment in questo tutorial:

    gcloud run services delete SERVICE-NAME

    Dove SERVICE-NAME è il nome del servizio che hai scelto.

    Puoi anche eliminare i servizi Cloud Run for Anthos dalla console Google Cloud:

    Vai a Cloud Run for Anthos

  2. Rimuovi le configurazioni gcloud predefinite che hai aggiunto durante la configurazione del tutorial:

     gcloud config unset run/platform
     gcloud config unset run/cluster
     gcloud config unset run/cluster_location
    
  3. Rimuovi la configurazione del progetto:

     gcloud config unset project
    
  4. Elimina altre risorse Google Cloud create in questo tutorial:

Passaggi successivi

  • Scopri di più sulla persistenza dei dati con Cloud Run for Anthos tramite Cloud Storage
  • Scopri come utilizzare l'API Cloud Vision per rilevare contenuti diversi dai contenuti espliciti.
  • Esplora architetture di riferimento, diagrammi e best practice su Google Cloud. Dai un'occhiata al nostro Centro di architettura cloud.