顔検出のチュートリアル


目標

このサンプルでは、Google Vision API を使用して画像の中の顔を検出します。顔が正しく検出されたことを証明するために、このデータを使用して顔のそれぞれを囲むボックスを描画します。

費用

このドキュメントでは、Google Cloud の次の課金対象のコンポーネントを使用します。

  • Cloud Vision

料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。 新しい Google Cloud ユーザーは無料トライアルをご利用いただける場合があります。

始める前に

  1. Google Cloud アカウントにログインします。Google Cloud を初めて使用する場合は、アカウントを作成して、実際のシナリオでの Google プロダクトのパフォーマンスを評価してください。新規のお客様には、ワークロードの実行、テスト、デプロイができる無料クレジット $300 分を差し上げます。
  2. Google Cloud Console の [プロジェクト セレクタ] ページで、Google Cloud プロジェクトを選択または作成します。

    プロジェクト セレクタに移動

  3. Google Cloud プロジェクトで課金が有効になっていることを確認します

  4. Google Cloud Vision API を有効にします。

    API を有効にする

  5. Google Cloud Console の [プロジェクト セレクタ] ページで、Google Cloud プロジェクトを選択または作成します。

    プロジェクト セレクタに移動

  6. Google Cloud プロジェクトで課金が有効になっていることを確認します

  7. Google Cloud Vision API を有効にします。

    API を有効にする

  8. アプリケーションのデフォルト認証情報を使用するように環境を設定します。
  9. 言語固有のタスクとツールを設定します。

    C#

    Java

    • Java をインストールします。
    • API リファレンス
    • Apache Maven ビルドシステムをダウンロードしてインストールします。Maven を使用すると、Google API クライアント ライブラリと Vision API のクライアント ライブラリがインストールされている状態でプロジェクトをビルドできます。これは、これらのライブラリが pom.xml で指定されているためです。

      <dependency>
        <groupId>com.google.apis</groupId>
        <artifactId>google-api-services-vision</artifactId>
        <version>v1-rev20231219-2.0.0</version>
      </dependency>
      <dependency>
        <groupId>com.google.auth</groupId>
        <artifactId>google-auth-library-oauth2-http</artifactId>
      </dependency>
      <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
      </dependency>
      <dependency>
        <groupId>com.google.http-client</groupId>
        <artifactId>google-http-client-jackson2</artifactId>
      </dependency>
      ...

    Node.js

    • Google クライアント ライブラリをインストールします。
    • node.js をインストールします。
    • API リファレンス
    • npmnode-canvas をインストールします。サンプルコードには、npm install コマンドを使用してすべての依存関係をインストールする package.json が含まれています。node-canvas には、この他にもインストールが必要な依存関係が存在することがあります。詳しくは、node-canvas のインストール ドキュメントをご覧ください。

      {
        "name": "nodejs-docs-samples-vision",
        "private": true,
        "license": "Apache-2.0",
        "author": "Google LLC",
        "engines": {
          "node": ">=16.0.0"
        },
        "files": [
          "*.js"
        ],
        "scripts": {
          "test": "c8 mocha -p -j 2 system-test --timeout 600000"
        },
        "dependencies": {
          "@google-cloud/vision": "^4.0.0",
          "natural": "^6.2.0",
          "pureimage": "^0.3.17",
          "redis": "^4.6.5",
          "yargs": "^17.7.1"
        },
        "devDependencies": {
          "@google-cloud/storage": "^7.0.0",
          "@types/uuid": "^9.0.1",
          "@types/yargs": "^17.0.22",
          "c8": "^8.0.0",
          "chai": "^4.3.7",
          "mocha": "^10.2.0",
          "uuid": "^9.0.0"
        }
      }
      

    PHP

    Python

    Ruby

サービス オブジェクトを作成する

Google の API に公式クライアント SDK を使用してアクセスするには、その API のディスカバリ ドキュメントに基づいてサービス オブジェクトを作成します。このドキュメントは、SDK に対する API を記述するものです。デベロッパーは、自分の認証情報を使用してこのドキュメントを Vision API のディスカバリ サービスから取得する必要があります。

Java

import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.vision.v1.Vision;
import com.google.api.services.vision.v1.VisionScopes;
import com.google.api.services.vision.v1.model.AnnotateImageRequest;
import com.google.api.services.vision.v1.model.AnnotateImageResponse;
import com.google.api.services.vision.v1.model.BatchAnnotateImagesRequest;
import com.google.api.services.vision.v1.model.BatchAnnotateImagesResponse;
import com.google.api.services.vision.v1.model.FaceAnnotation;
import com.google.api.services.vision.v1.model.Feature;
import com.google.api.services.vision.v1.model.Image;
import com.google.api.services.vision.v1.model.Vertex;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.common.collect.ImmutableList;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.util.List;
import javax.imageio.ImageIO;
/** Connects to the Vision API using Application Default Credentials. */
public static Vision getVisionService() throws IOException, GeneralSecurityException {
  GoogleCredentials credential =
      GoogleCredentials.getApplicationDefault().createScoped(VisionScopes.all());
  JsonFactory jsonFactory = GsonFactory.getDefaultInstance();
  return new Vision.Builder(
          GoogleNetHttpTransport.newTrustedTransport(),
          jsonFactory,
          new HttpCredentialsAdapter(credential))
      .setApplicationName(APPLICATION_NAME)
      .build();
}

Node.js

// By default, the client will authenticate using the service account file
// specified by the GOOGLE_APPLICATION_CREDENTIALS environment variable and use
// the project specified by the GCLOUD_PROJECT environment variable. See
// https://googlecloudplatform.github.io/gcloud-node/#/docs/google-cloud/latest/guides/authentication
const vision = require('@google-cloud/vision');
// Creates a client
const client = new vision.ImageAnnotatorClient();

const fs = require('fs');

Python

from google.cloud import vision
from PIL import Image, ImageDraw
client = vision.ImageAnnotatorClient()

顔検出リクエストを送信する

Vision API へのリクエストを作成するには、最初に API のドキュメントを調べます。この場合、images リソースを annotate 画像へリクエストします。この API へのリクエストは、requests リストを持つオブジェクト形式になります。このリスト内のアイテムのそれぞれに、次の 2 つの情報が格納されます。

  • base64 エンコード済みの画像データ
  • その画像についてアノテーションを付けたい機能のリスト

この例では、1 つの画像の FACE_DETECTION アノテーションをリクエストし、該当する画像部分のレスポンスを返します。

Java

/** Gets up to {@code maxResults} faces for an image stored at {@code path}. */
public List<FaceAnnotation> detectFaces(Path path, int maxResults) throws IOException {
  byte[] data = Files.readAllBytes(path);

  AnnotateImageRequest request =
      new AnnotateImageRequest()
          .setImage(new Image().encodeContent(data))
          .setFeatures(
              ImmutableList.of(
                  new Feature().setType("FACE_DETECTION").setMaxResults(maxResults)));
  Vision.Images.Annotate annotate =
      vision
          .images()
          .annotate(new BatchAnnotateImagesRequest().setRequests(ImmutableList.of(request)));
  // Due to a bug: requests to Vision API containing large images fail when GZipped.
  annotate.setDisableGZipContent(true);

  BatchAnnotateImagesResponse batchResponse = annotate.execute();
  assert batchResponse.getResponses().size() == 1;
  AnnotateImageResponse response = batchResponse.getResponses().get(0);
  if (response.getFaceAnnotations() == null) {
    throw new IOException(
        response.getError() != null
            ? response.getError().getMessage()
            : "Unknown error getting image annotations");
  }
  return response.getFaceAnnotations();
}

Node.js

async function detectFaces(inputFile) {
  // Make a call to the Vision API to detect the faces
  const request = {image: {source: {filename: inputFile}}};
  const results = await client.faceDetection(request);
  const faces = results[0].faceAnnotations;
  const numFaces = faces.length;
  console.log(`Found ${numFaces} face${numFaces === 1 ? '' : 's'}.`);
  return faces;
}

Python

def detect_face(face_file, max_results=4):
    """Uses the Vision API to detect faces in the given file.

    Args:
        face_file: A file-like object containing an image with faces.

    Returns:
        An array of Face objects with information about the picture.
    """
    client = vision.ImageAnnotatorClient()

    content = face_file.read()
    image = vision.Image(content=content)

    return client.face_detection(image=image, max_results=max_results).face_annotations

レスポンスを処理する

画像の中の顔が検出されました。顔アノテーション リクエストに対するレスポンスの中には、検出された顔についての多数のメタデータが含まれています。このメタデータの中には、その顔を囲むポリゴンの座標があります。ただし、この時点では、これは単なる数字のリストです。これを使って、画像の中の顔が確かに検出されたことを確認しましょう。Vision API から返された座標を使用して、画像のコピーの上にポリゴンを描画します。

Java

/** Reads image {@code inputPath} and writes {@code outputPath} with {@code faces} outlined. */
private static void writeWithFaces(Path inputPath, Path outputPath, List<FaceAnnotation> faces)
    throws IOException {
  BufferedImage img = ImageIO.read(inputPath.toFile());
  annotateWithFaces(img, faces);
  ImageIO.write(img, "jpg", outputPath.toFile());
}

/** Annotates an image {@code img} with a polygon around each face in {@code faces}. */
public static void annotateWithFaces(BufferedImage img, List<FaceAnnotation> faces) {
  for (FaceAnnotation face : faces) {
    annotateWithFace(img, face);
  }
}

/** Annotates an image {@code img} with a polygon defined by {@code face}. */
private static void annotateWithFace(BufferedImage img, FaceAnnotation face) {
  Graphics2D gfx = img.createGraphics();
  Polygon poly = new Polygon();
  for (Vertex vertex : face.getFdBoundingPoly().getVertices()) {
    poly.addPoint(vertex.getX(), vertex.getY());
  }
  gfx.setStroke(new BasicStroke(5));
  gfx.setColor(new Color(0x00ff00));
  gfx.draw(poly);
}

Node.js

node-canvas ライブラリを使用して画像の上に描画します。

async function highlightFaces(inputFile, faces, outputFile, PImage) {
  // Open the original image
  const stream = fs.createReadStream(inputFile);
  let promise;
  if (inputFile.match(/\.jpg$/)) {
    promise = PImage.decodeJPEGFromStream(stream);
  } else if (inputFile.match(/\.png$/)) {
    promise = PImage.decodePNGFromStream(stream);
  } else {
    throw new Error(`Unknown filename extension ${inputFile}`);
  }
  const img = await promise;
  const context = img.getContext('2d');
  context.drawImage(img, 0, 0, img.width, img.height, 0, 0);

  // Now draw boxes around all the faces
  context.strokeStyle = 'rgba(0,255,0,0.8)';
  context.lineWidth = '5';

  faces.forEach(face => {
    context.beginPath();
    let origX = 0;
    let origY = 0;
    face.boundingPoly.vertices.forEach((bounds, i) => {
      if (i === 0) {
        origX = bounds.x;
        origY = bounds.y;
        context.moveTo(bounds.x, bounds.y);
      } else {
        context.lineTo(bounds.x, bounds.y);
      }
    });
    context.lineTo(origX, origY);
    context.stroke();
  });

  // Write the result to a file
  console.log(`Writing to file ${outputFile}`);
  const writeStream = fs.createWriteStream(outputFile);
  await PImage.encodePNGToStream(img, writeStream);
}

Python

def highlight_faces(image, faces, output_filename):
    """Draws a polygon around the faces, then saves to output_filename.

    Args:
      image: a file containing the image with the faces.
      faces: a list of faces found in the file. This should be in the format
          returned by the Vision API.
      output_filename: the name of the image file to be created, where the
          faces have polygons drawn around them.
    """
    im = Image.open(image)
    draw = ImageDraw.Draw(im)
    # Sepecify the font-family and the font-size
    for face in faces:
        box = [(vertex.x, vertex.y) for vertex in face.bounding_poly.vertices]
        draw.line(box + [box[0]], width=5, fill="#00ff00")
        # Place the confidence value/score of the detected faces above the
        # detection box in the output image
        draw.text(
            (
                (face.bounding_poly.vertices)[0].x,
                (face.bounding_poly.vertices)[0].y - 30,
            ),
            str(format(face.detection_confidence, ".3f")) + "%",
            fill="#FF0000",
        )
    im.save(output_filename)

すべてをまとめる

Java

/** Annotates an image using the Vision API. */
public static void main(String[] args) throws IOException, GeneralSecurityException {
  if (args.length != 2) {
    System.err.println("Usage:");
    System.err.printf(
        "\tjava %s inputImagePath outputImagePath\n", FaceDetectApp.class.getCanonicalName());
    System.exit(1);
  }
  Path inputPath = Paths.get(args[0]);
  Path outputPath = Paths.get(args[1]);
  if (!outputPath.toString().toLowerCase().endsWith(".jpg")) {
    System.err.println("outputImagePath must have the file extension 'jpg'.");
    System.exit(1);
  }

  FaceDetectApp app = new FaceDetectApp(getVisionService());
  List<FaceAnnotation> faces = app.detectFaces(inputPath, MAX_RESULTS);
  System.out.printf("Found %d face%s\n", faces.size(), faces.size() == 1 ? "" : "s");
  System.out.printf("Writing to file %s\n", outputPath);
  app.writeWithFaces(inputPath, outputPath, faces);
}
...

サンプルをビルドして実行するには、サンプルコード ディレクトリの次のコマンドを実行します。

mvn clean compile assembly:single
java -cp target/vision-face-detection-1.0-SNAPSHOT-jar-with-dependencies.jar \
    com.google.cloud.vision.samples.facedetect.FaceDetectApp \
    data/face.jpg \
    output.jpg

Node.js

async function main(inputFile, outputFile) {
  const PImage = require('pureimage');
  outputFile = outputFile || 'out.png';
  const faces = await detectFaces(inputFile);
  console.log('Highlighting...');
  await highlightFaces(inputFile, faces, outputFile, PImage);
  console.log('Finished!');
}

サンプルを実行するには、サンプルコードのディレクトリの次のコマンドを実行します。

node faceDetection resources/face.png

Python

def main(input_filename, output_filename, max_results):
    with open(input_filename, "rb") as image:
        faces = detect_face(image, max_results)
        print("Found {} face{}".format(len(faces), "" if len(faces) == 1 else "s"))

        print(f"Writing to file {output_filename}")
        # Reset the file pointer, so we can read the file again
        image.seek(0)
        highlight_faces(image, faces, output_filename)

クリーンアップ

このチュートリアルで使用したリソースについて、Google Cloud アカウントに課金されないようにするには、リソースを含むプロジェクトを削除するか、プロジェクトを維持して個々のリソースを削除します。

  1. Google Cloud コンソールで、[リソースの管理] ページに移動します。

    [リソースの管理] に移動

  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。