Instructivo de ImageMagick

En este instructivo, se muestra cómo usar Cloud Functions, la API de Google Cloud Vision e ImageMagick para detectar y difuminar imágenes ofensivas que se suben al depósito de Cloud Storage.

Objetivos

  • Implementar una función de Cloud Functions en segundo plano activada por Storage
  • Usar la API de Cloud Vision para detectar contenido violento o destinado para adultos
  • Usar ImageMagick para difuminar imágenes ofensivas
  • Probar la función con solo subir una imagen de un zombi que come carne humana

Costos

Este instructivo usa componentes facturables de Cloud Platform, incluidos los siguientes:

  • Google Cloud Functions
  • Google Cloud Storage
  • API de Google Cloud Vision

Usa la calculadora de precios para generar una estimación de los costos según el uso previsto.

Los usuarios nuevos de Cloud Platform pueden optar por una prueba gratuita.

Antes de comenzar

  1. Accede a tu Cuenta de Google.

    Si todavía no tienes una cuenta, regístrate para obtener una nueva.

  2. Selecciona o crea un proyecto de GCP.

    Ir a la página Administrar recursos

  3. Comprueba que la facturación esté habilitada en tu proyecto.

    Descubre cómo puedes habilitar la facturación

  4. Habilita las Cloud Functions, Cloud Storage y Cloud Vision API necesarias.

    Habilita las API

  5. Realiza la instalación y la inicialización del SDK de Cloud.
  6. Actualiza los componentes de gcloud:
    gcloud components update
  7. Prepara tu entorno de desarrollo.

Visualiza el flujo de datos

El flujo de datos en la aplicación de instructivo de ImageMagick incluye varios pasos como se muestra a continuación:

  1. Se sube una imagen a un depósito de Cloud Storage.
  2. La función de Cloud Functions analiza la imagen con la API de Cloud Vision.
  3. Si se detecta contenido violento o destinado a adultos, la función de Cloud Functions usa ImageMagick para difuminar la imagen.
  4. La imagen difuminada se sube a otro depósito de Cloud Storage para su utilización.

Cómo preparar la aplicación

  1. Crea un depósito de Cloud Storage para subir imágenes, en el que YOUR_INPUT_BUCKET_NAME es un nombre de depósito único a nivel global:

    gsutil mb gs://YOUR_INPUT_BUCKET_NAME
    
  2. Crea un depósito de Cloud Storage para recibir las imágenes difuminadas; YOUR_OUTPUT_BUCKET_NAME es un nombre de depósito único a nivel global:

    gsutil mb gs://YOUR_OUTPUT_BUCKET_NAME
    
  3. Clona el repositorio de la app de muestra en tu máquina local:

    Node.js

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

    De manera opcional, puedes descargar la muestra como un archivo zip y extraerla.

    Python

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

    De manera opcional, puedes descargar la muestra como un archivo zip y extraerla.

    Go

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

    De manera opcional, puedes descargar la muestra como un archivo zip y extraerla.

  4. Ve al directorio que contiene el código de muestra de Cloud Functions, como sigue:

    Node.js

    cd nodejs-docs-samples/functions/imagemagick/

    Python

    cd python-docs-samples/functions/imagemagick/

    Go

    cd golang-samples/functions/imagemagick/

Comprensión del código

Importa dependencias

La aplicación debe importar varias dependencias con el fin de interactuar con los servicios de Google Cloud Platform y con ImageMagick y el sistema de archivos:

Node.js 8/10

const gm = require('gm').subClass({imageMagick: true});
const fs = require('fs');
const path = require('path');
const {Storage} = require('@google-cloud/storage');
const storage = new Storage();
const vision = require('@google-cloud/vision').v1p1beta1;

const client = new vision.ImageAnnotatorClient();

const {BLURRED_BUCKET_NAME} = process.env;

Python

import os
import tempfile

from google.cloud import storage, vision
from wand.image import Image

storage_client = storage.Client()
vision_client = vision.ImageAnnotatorClient()

Go


// Package imagemagick contains an example of using ImageMagick from a Cloud
// Function.
package imagemagick

import (
	"context"
	"errors"
	"fmt"
	"log"
	"os"
	"os/exec"

	"cloud.google.com/go/storage"
	vision "cloud.google.com/go/vision/apiv1"
	visionpb "google.golang.org/genproto/googleapis/cloud/vision/v1"
)

// 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)
	}
}

Analiza imágenes

Se invoca la siguiente función cuando una imagen se sube al depósito de Cloud Storage que creaste para almacenar imágenes. La función usa la API de Cloud Vision para detectar contenido violento o destinado para adultos en imágenes que se suben.

Node.js 8/10

// Blurs uploaded images that are flagged as Adult or Violence.
exports.blurOffensiveImages = event => {
  const object = event.data || event; // Node 6: event.data === Node 8+: event

  // Exit if this is a deletion or a deploy event.
  if (object.resourceState === 'not_exists') {
    console.log('This is a deletion event.');
    return;
  } else if (!object.name) {
    console.log('This is a deploy event.');
    return;
  }

  const file = storage.bucket(object.bucket).file(object.name);
  const filePath = `gs://${object.bucket}/${object.name}`;

  // Ignore already-blurred files (to prevent re-invoking this function)
  if (file.name.startsWith('blurred-')) {
    console.log(`The image ${file.name} is already blurred.`);
    return;
  }

  console.log(`Analyzing ${file.name}.`);

  return client
    .safeSearchDetection(filePath)
    .catch(err => {
      console.error(`Failed to analyze ${file.name}.`, err);
      return Promise.reject(err);
    })
    .then(([result]) => {
      const detections = result.safeSearchAnnotation;

      if (
        detections.adult === 'VERY_LIKELY' ||
        detections.violence === 'VERY_LIKELY'
      ) {
        console.log(
          `The image ${file.name} has been detected as inappropriate.`
        );
        return blurImage(file, BLURRED_BUCKET_NAME);
      } else {
        console.log(`The image ${file.name} has been detected as OK.`);
      }
    });
};

Python

# Blurs uploaded images that are flagged as Adult or Violence.
def blur_offensive_images(data, context):
    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 = {'source': {'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(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: %v", 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
}

Difumina imágenes

La siguiente función recibe una llamada cuando se detecta contenido violento o destinado para adultos en una imagen que se sube. La función descarga la imagen ofensiva, usa ImageMagick para difuminarla y, luego, sube la imagen difuminada sobre la imagen original.

Node.js 8/10

// Blurs the given file using ImageMagick, and uploads it to another bucket.
function blurImage(file, blurredBucketName) {
  const tempLocalPath = `/tmp/${path.parse(file.name).base}`;

  // Download file from bucket.
  return file
    .download({destination: tempLocalPath})
    .catch(err => {
      console.error('Failed to download file.', err);
      return Promise.reject(err);
    })
    .then(() => {
      console.log(
        `Image ${file.name} has been downloaded to ${tempLocalPath}.`
      );

      // Blur the image using ImageMagick.
      return 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 {
              resolve(stdout);
            }
          });
      });
    })
    .then(() => {
      console.log(`Image ${file.name} has been blurred.`);

      // Upload result to a different bucket, to avoid re-triggering this function.
      // You can also re-upload it to the same bucket + tell your Cloud Function to
      // ignore files marked as blurred (e.g. those with a "blurred" prefix)
      const blurredBucket = storage.bucket(blurredBucketName);

      // Upload the Blurred image back into the bucket.
      return blurredBucket
        .upload(tempLocalPath, {destination: file.name})
        .catch(err => {
          console.error('Failed to upload blurred image.', err);
          return Promise.reject(err);
        });
    })
    .then(() => {
      console.log(
        `Blurred image has been uploaded to: gs://${blurredBucketName}/${file.name}`
      );

      // Delete the temporary file.
      return new Promise((resolve, reject) => {
        fs.unlink(tempLocalPath, err => {
          if (err) {
            reject(err);
          } else {
            resolve();
          }
        });
      });
    });
}

Python

# Blurs the given file using ImageMagick.
def __blur_image(current_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: %v", 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: %v", err)
	}

	log.Printf("Blurred image has been uploaded to %s", outputBlob.ObjectName())

	return nil
}

Implementa la función

  1. Para implementar tu función de Cloud con un activador de Storage, ejecuta el siguiente comando en el directorio imagemagick:

    Node.js 8

    gcloud functions deploy blurOffensiveImages --runtime nodejs8 --trigger-bucket YOUR_INPUT_BUCKET_NAME --set-env-vars BLURRED_BUCKET_NAME=YOUR_OUTPUT_BUCKET_NAME

    Node.js 10 (Beta)

    gcloud functions deploy blurOffensiveImages --runtime nodejs10 --trigger-bucket YOUR_INPUT_BUCKET_NAME --set-env-vars BLURRED_BUCKET_NAME=YOUR_OUTPUT_BUCKET_NAME

    Python

    gcloud functions deploy blur_offensive_images --runtime python37 --trigger-bucket YOUR_INPUT_BUCKET_NAME --set-env-vars BLURRED_BUCKET_NAME=YOUR_OUTPUT_BUCKET_NAME

    Go

    gcloud functions deploy BlurOffensiveImages --runtime go111 --trigger-bucket YOUR_INPUT_BUCKET_NAME --set-env-vars BLURRED_BUCKET_NAME=YOUR_OUTPUT_BUCKET_NAME

    donde YOUR_INPUT_BUCKET_NAME es el nombre del depósito de Cloud Storage en que se suben imágenes, y YOUR_OUTPUT_BUCKET_NAME es el nombre del depósito en que se deberían guardar las imágenes difuminadas.

Cómo subir una imagen

  1. Sube una imagen ofensiva, como esta imagen de un zombi que come carne humana:

    gsutil cp zombie.jpg gs://YOUR_INPUT_BUCKET_NAME
    

    donde YOUR_INPUT_BUCKET_NAME es el depósito de Cloud Storage que creaste previamente para subir imágenes.

  2. Revisa los registros para asegurarte de que las ejecuciones se completaron:

    gcloud functions logs read --limit 100
    
  3. Puedes ver las imágenes difuminadas en el depósito de Cloud Storage YOUR_OUTPUT_BUCKET_NAME que creaste previamente.

Limpia

Sigue estos pasos para evitar que se apliquen cargos a tu cuenta de Google Cloud Platform por los recursos que usaste en este instructivo:

Cómo borrar el proyecto

La manera más fácil de eliminar la facturación es borrar el proyecto que creaste para el instructivo.

Para borrar el proyecto, haz lo siguiente:

  1. En la GCP Console, dirígete a la página Proyectos.

    Ir a la página Proyectos

  2. En la lista de proyectos, selecciona el proyecto que deseas borrar y haz clic en Borrar.
  3. En el cuadro de diálogo, escribe el ID del proyecto y, luego, haz clic en Cerrar para borrar el proyecto.

Borra la función de Cloud Functions

Borrar las funciones de Cloud Functions no quita ningún recurso almacenado en Cloud Storage.

Para borrar la función de Cloud Functions que implementaste en este instructivo, ejecuta el siguiente comando:

Node.js

gcloud functions delete blurOffensiveImages 

Python

gcloud functions delete blur_offensive_images 

Go

gcloud functions delete BlurOffensiveImages 

También puedes borrar las funciones de Cloud Functions desde Google Cloud Platform Console.

¿Te ha resultado útil esta página? Enviar comentarios:

Enviar comentarios sobre...

Documentación de Cloud Functions