Instructivo de ImageMagick

En este instructivo, se muestra cómo usar Cloud Functions, ImageMagick y la API de Google Cloud Vision 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

En este instructivo, se usan los siguientes componentes facturables de Cloud Platform:

  • 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.

New Cloud Platform users might be eligible for a free trial.

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, and 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 los siguientes pasos:

  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 uso.

Prepara la aplicación

  1. Crea un depósito de Cloud Storage para subir imágenes, donde 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, donde 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 aplicación de muestra en tu máquina local con el siguiente comando:

    Node.js

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

    De forma alternativa, puedes descargar la muestra como un archivo ZIP y extraerla.

    Python

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

    De forma alternativa, puedes descargar la muestra como un archivo ZIP y extraerla.

    Go

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

    De forma alternativa, puedes descargar la muestra como un archivo ZIP y extraerla.

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

    Node.js

    cd nodejs-docs-samples/functions/imagemagick/

    Python

    cd python-docs-samples/functions/imagemagick/

    Go

    cd golang-samples/functions/imagemagick/

Comprende el código

Importa dependencias

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

Node.js 8/10

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

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 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"
	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 a fin de detectar contenido violento o para adultos en imágenes que se suben.

Node.js 8/10

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

# 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 y reemplaza la imagen original.

Node.js 8/10

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

# 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 uploaded to gs://%s/%s", outputBlob.BucketName(), outputBlob.ObjectName())

	return nil
}

Implementa la función

  1. Para implementar la función de Cloud Functions 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 el que se suben las imágenes y YOUR_OUTPUT_BUCKET_NAME es el nombre del depósito en el que se deberían guardar las imágenes difuminadas.

Sube una imagen

  1. Sube una imagen ofensiva, como la 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 antes.

Realiza una limpieza

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, sigue estos pasos:

  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