カスタム データセットで Detectron2 と PyTorch を使用した顔検出

この記事では、Python を使用して、カスタムの顔検出データセットで事前トレーニングされたオブジェクト検出モデルを微調整する方法について説明します。Detectron2 および PyTorch 用のカスタム顔検出データセットを準備し、事前トレーニングされたモデルを微調整して画像内の顔の境界を見つける方法を学びます。

顔検出は、画像内で人間の顔を(境界)見つけ出すタスクです。これは次のような場合に役立ちます。

  • セキュリティシステム(本人確認の第一歩)

  • オートフォーカスと笑顔検出で素晴らしい写真を撮影

  • マーケティング目的で年齢、人種、感情状態を検出します

1b5f0fcaea773df53fcb08e057aff96f.png

歴史的に、これは非常に厄介な問題でした。最先端の技術を向上させるために、広範な手動特徴量エンジニアリング、新しいアルゴリズムと手法が開発されています。

顔検出モデルは、最近ではほぼすべてのコンピューター ビジョン パッケージ/フレームワークに含まれています。最もパフォーマンスの高いモデルの一部は深層学習手法を使用しています。たとえば、OpenCV はカスケード分類器などのさまざまなツールを提供します。

このガイドでは、次の方法を学習します。

  • Detectron2 で使用する顔検出用のカスタム データセットを準備する

  • 最先端の(に近い)物体検出モデルを使用して画像内の顔を検出します

  • この作業を顔認識にも拡張できます

ディテクトロン2

Detectron2 は、Facebook Research チームによって開発された、最先端の物体検出および画像セグメンテーション モデルを構築するためのフレームワークです。Detectron2 は、最初のバージョンを完全に書き直したものです。Detectron2 は PyTorch (最新バージョンと互換性あり) を使用し、超高速トレーニングを可能にします。詳細については、Facebook Research の入門ブログ投稿をご覧ください。

Detectron2 の真の威力は、モデル ズーで利用できる多数の事前トレーニング済みモデルです。しかし、独自のデータセットで微調整できなければ、何の意味があるでしょうか? 幸いなことに、それはとても簡単です。このガイドでは、これを行う方法を説明します。

Detectron2をインストールする

この記事の執筆時点では、Detectron2 はまだアルファ段階にあります。公式リリースはありますが、master ブランチからクローンしてコンパイルします。これはバージョン 0.1 と同じである必要があります。まずいくつかの要件をインストールしましょう。

!pip install -q cython pyyaml == 5.1 
!pip install -q -U 'git+https://github.com/cocodataset/cocoapi.git#subdirectory=PythonAPI'

次に、Detectron2 パッケージをダウンロード、コンパイル、インストールします。 

!git clone https://github.com/facebookresearch/detectron2 detectron2_repo 
!pip install -q -e detectron2_repo

この時点で、続行するにはノートブック ランタイムを再起動する必要があります。

%reload_ext watermark %watermark -v -p numpy,pandas,pycocotools,torch,torchvision,detectron2
CPython 3.6.9
IPython 5.5.0
numpy 1.17.5
pandas 0.25.3
pycocotools 2.0
torch 1.4.0
torchvision 0.5.0
detectron2 0.1
import torch, torchvision
import detectron2
from detectron2.utils.logger import setup_logger
setup_logger()


import glob


import os
import ntpath
import numpy as np
import cv2
import random
import itertools
import pandas as pd
from tqdm import tqdm
import urllib
import json
import PIL.Image as Image


from detectron2 import model_zoo
from detectron2.engine import DefaultPredictor, DefaultTrainer
from detectron2.config import get_cfg
from detectron2.utils.visualizer import Visualizer, ColorMode
from detectron2.data import DatasetCatalog, MetadataCatalog, build_detection_test_loader
from detectron2.evaluation import COCOEvaluator, inference_on_dataset
from detectron2.structures import BoxMode


import seaborn as sns
from pylab import rcParams
import matplotlib.pyplot as plt
from matplotlib import rc


%matplotlib inline
%config InlineBackend.figure_format='retina'


sns.set(style='whitegrid', palette='muted', font_scale=1.2)


HAPPY_COLORS_PALETTE = ["#01BEFE", "#FFDD00", "#FF7D00", "#FF006D", "#ADFF02", "#8F00FF"]


sns.set_palette(sns.color_palette(HAPPY_COLORS_PALETTE))


rcParams['figure.figsize'] = 12, 8


RANDOM_SEED = 42
np.random.seed(RANDOM_SEED)
torch.manual_seed(RANDOM_SEED)

顔検出データ

このデータセットはパブリック ドメインで無料で利用できます。これは Dataturks によって提供され、Kaggle: 画像内の境界ボックスでラベル付けされた面でホストされています。約 500 の画像があり、約 1100 の顔が境界ボックスによって手動でラベル付けされています。

コメントを含む JSON ファイルをダウンロードし、Google ドライブにアップロードしました。それを取得しましょう:

!gdown --id 1K79wJgmPTWamqb04Op2GxW0SW9oxw8KS

ファイルを Pandas データフレームにロードしましょう。

faces_df = pd.read_json('face_detection.json', lines=True)

各行には 1 つの顔の注釈が含まれています。複数の行が単一の画像を参照する場合があることに注意してください (たとえば、画像ごとに複数の顔)。

データの前処理

データセットには画像の URL と注釈のみが含まれます。これらの画像をダウンロードする必要があります。また、後ほど Detectron2 で使いやすくするために、注釈を正規化します。

os.makedirs("faces", exist_ok=True)


dataset = []


for index, row in tqdm(faces_df.iterrows(), total=faces_df.shape[0]):
    img = urllib.request.urlopen(row["content"])
    img = Image.open(img)
    img = img.convert('RGB')


    image_name = f'face_{index}.jpeg'


    img.save(f'faces/{image_name}', "JPEG")


    annotations = row['annotation']
    for an in annotations:


      data = {}


      width = an['imageWidth']
      height = an['imageHeight']
      points = an['points']


      data['file_name'] = image_name
      data['width'] = width
      data['height'] = height


      data["x_min"] = int(round(points[0]["x"] * width))
      data["y_min"] = int(round(points[0]["y"] * height))
      data["x_max"] = int(round(points[1]["x"] * width))
      data["y_max"] = int(round(points[1]["y"] * height))


      data['class_name'] = 'face'


      dataset.append(data)

もっと詳しく見ることができるように、データをデータ フレームに入れてみましょう。

df = pd.DataFrame(dataset)
print(df.file_name.unique().shape[0], df.shape[0])
409 1132

合計 409 枚の画像 (約束の 500 枚よりはるかに少ない) と 1132 個の注釈があります。それらをディスクに保存しましょう (再利用できるようにします)。

データ

アノテーション データのサンプルをいくつか見てみましょう。OpenCV を使用して画像を読み込み、境界ボックスを追加し、サイズを変更します。これらすべてを行うためのヘルパー関数を定義します。

def annotate_image(annotations, resize=True):
  file_name = annotations.file_name.to_numpy()[0]
  img = cv2.cvtColor(cv2.imread(f'faces/{file_name}'), cv2.COLOR_BGR2RGB)


  for i, a in annotations.iterrows():
    cv2.rectangle(img, (a.x_min, a.y_min), (a.x_max, a.y_max), (0, 255, 0), 2)


  if not resize:
    return img


  return cv2.resize(img, (384, 384), interpolation = cv2.INTER_AREA)

まず、注釈付きの画像をいくつか表示してみましょう。

5cd702ae92b05f2ecba0f1a2e0cdfd68.png

f6740914dcb440fcd84269b6d24ebff5.png

これらは素晴らしい画像であり、注釈もはっきりと見えます。torchvision を使用して画像のグリッドを作成できます。画像のサイズが異なることに注意してください。そのため、サイズを変更します。

2c212409437c164de8cc81fa25a9b218.png

いくつかの注釈が欠落していることがはっきりとわかります (列 4)。これは現実のデータであり、場合によっては特定の方法で処理する必要があります。

Detectron 2 による顔検出

次に、カスタム データセットを使用してモデルを微調整する手順を説明します。ただし、その前に、テスト用にデータの 5% を保存しておきます。

df = pd.read_csv('annotations.csv')


IMAGES_PATH = f'faces'


unique_files = df.file_name.unique()


train_files = set(np.random.choice(unique_files, int(len(unique_files) * 0.95), replace=False))
train_df = df[df.file_name.isin(train_files)]
test_df = df[~df.file_name.isin(train_files)]

ここでは、ファイル名間で分割したいため、古典的なトレーニングとテストの分割は適用されません。

次のセクションは、やや一般的な方法で書かれています。明らかに、クラスは 1 つだけ、つまり顔しかありません。ただし、カテゴリをさらに追加することは、データフレームに注釈を追加するのと同じくらい簡単である必要があります。

classes = df.class_name.unique().tolist()

次に、データセットを Detectron2 に変換する関数を作成します。

def create_dataset_dicts(df, classes):
  dataset_dicts = []
  for image_id, img_name in enumerate(df.file_name.unique()):


    record = {}


    image_df = df[df.file_name == img_name]


    file_path = f'{IMAGES_PATH}/{img_name}'
    record["file_name"] = file_path
    record["image_id"] = image_id
    record["height"] = int(image_df.iloc[0].height)
    record["width"] = int(image_df.iloc[0].width)


    objs = []
    for _, row in image_df.iterrows():


      xmin = int(row.x_min)
      ymin = int(row.y_min)
      xmax = int(row.x_max)
      ymax = int(row.y_max)


      poly = [
          (xmin, ymin), (xmax, ymin),
          (xmax, ymax), (xmin, ymax)
      ]
      poly = list(itertools.chain.from_iterable(poly))


      obj = {
        "bbox": [xmin, ymin, xmax, ymax],
        "bbox_mode": BoxMode.XYXY_ABS,
        "segmentation": [poly],
        "category_id": classes.index(row.class_name),
        "iscrowd": 0
      }
      objs.append(obj)


    record["annotations"] = objs
    dataset_dicts.append(record)
  return dataset_dicts

使用される形式の機能: コメントの各行を、コメントのリストを含む単一のレコードに変換します。また、境界ボックスとまったく同じ形状のポリゴンを構築していることに気づくかもしれません。これは、Detectron2 の画像セグメンテーション モデルに必要です。

データセットをデータセットとメタデータ カタログに登録する必要があります。

for d in ["train", "val"]:
  DatasetCatalog.register("faces_" + d, lambda d=d: create_dataset_dicts(train_df if d == "train" else test_df, classes))
  MetadataCatalog.get("faces_" + d).set(thing_classes=classes)


statement_metadata = MetadataCatalog.get("faces_train")

残念ながら、テスト セットの推定器はデフォルトでは含まれていません。これは、独自のトレーナーを作成することで簡単に修正できます。

class CocoTrainer(DefaultTrainer):


  @classmethod
  def build_evaluator(cls, cfg, dataset_name, output_folder=None):


    if output_folder is None:
        os.makedirs("coco_eval", exist_ok=True)
        output_folder = "coco_eval"


    return COCOEvaluator(dataset_name, cfg, False, output_folder)

フォルダーを指定しない場合、評価結果は coco_eval フォルダーに保存されます。

Detectron2 モデルでの微調整は、PyTorch コードの作成とはまったく異なります。構成ファイルをロードし、いくつかの値を変更して、トレーニング プロセスを開始します。でも、自分が何をしているのかを知っていれば、本当に役立ちます。このチュートリアルでは、マスク R-CNN X101-FPN モデルを使用します。COCO データセットで事前トレーニングされており、非常に優れたパフォーマンスを発揮します。欠点はトレーニングが遅いことです。

構成ファイルと事前トレーニングされたモデルの重みをロードしましょう。

cfg = get_cfg()


cfg.merge_from_file(
  model_zoo.get_config_file(
    "COCO-InstanceSegmentation/mask_rcnn_X_101_32x8d_FPN_3x.yaml"
  )
)


cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url(
  "COCO-InstanceSegmentation/mask_rcnn_X_101_32x8d_FPN_3x.yaml"
)

トレーニングと評価に使用するデータセットを指定します (これらのデータセットを登録しました)。

cfg.DATASETS.TRAIN = ("faces_train",)
cfg.DATASETS.TEST = ("faces_val",)
cfg.DATALOADER.NUM_WORKERS = 4

オプティマイザーに関しては、適切な値に収束するためにいくつかの魔法を実行します。

cfg.SOLVER.IMS_PER_BATCH = 4
cfg.SOLVER.BASE_LR = 0.001
cfg.SOLVER.WARMUP_ITERS = 1000
cfg.SOLVER.MAX_ITER = 1500
cfg.SOLVER.STEPS = (1000, 1500)
cfg.SOLVER.GAMMA = 0.05

標準的なもの (バッチ サイズ、最大反復数、学習率) に加えて、いくつかの興味深いパラメーターがあります。

  • WARMUP_ITERS - 学習率は 0 から始まり、この反復回数で事前設定値まで徐々に増加します。

  • STEPS - 学習率がチェックポイントで減少する回数 (反復数)

最後に、テスト セットで評価するクラスの数とエポックを指定します。

cfg.MODEL.ROI_HEADS.BATCH_SIZE_PER_IMAGE = 64
cfg.MODEL.ROI_HEADS.NUM_CLASSES = len(classes)


cfg.TEST.EVAL_PERIOD = 500

カスタム トレーナーを使用してトレーニングを開始します。

os.makedirs(cfg.OUTPUT_DIR, exist_ok=True)


trainer = CocoTrainer(cfg)
trainer.resume_or_load(resume=False)
trainer.train()

物体検出モデルを評価する

物体検出モデルの評価は、標準の分類モデルや回帰モデルの評価とは少し異なります。知っておく必要がある主な指標は IoU (Intersection over Union) です。予測された境界と真実の 2 つの境界間の重複の程度を測定します。0から1までの値を取得できます。

a363c6b48bd9d29d9f2e4509c5cc8eb5.png

IoU を使用すると、予測が真陽性 (TP) か偽陽性 (FP) かを分類するためのしきい値 (例: >0.5) を定義できます。これで、精度-再現率曲線の下の面積を取得することで平均精度 (AP) を計算できます。AP@X (AP50 など) は、IoU しきい値の下にある AP にすぎません。これにより、物体検出モデルを評価する方法について実用的なアイデアが得られるはずです。

トレーニングが完了するまで待つ必要がないように、事前トレーニングされたモデルを準備しました。それをダウンロードします:

!gdown --id 18Ev2bpdKsBaDufhVKf0cT6RmM3FjW3nL 
!mv face_detector.pth output/model_final.pth

モデルをロードし、予測が正しいとみなされるための最小 85% の信頼しきい値を設定することで、予測を開始できます。

cfg.MODEL.WEIGHTS = os.path.join(cfg.OUTPUT_DIR, "model_final.pth")
cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = 0.85
predictor = DefaultPredictor(cfg)

トレーニングされたモデルを使用して推定器を実行します。

evaluator = COCOEvaluator("faces_val", cfg, False, output_dir="./output/")
val_loader = build_detection_test_loader(cfg, "faces_val")
inference_on_dataset(trainer.model, val_loader, evaluator)

画像内の顔を検索する

次に、フォルダーを作成し、テスト セット内のすべての画像を予測アノテーションとともに保存しましょう。

os.makedirs("annotated_results", exist_ok=True)


test_image_paths = test_df.file_name.unique()
for clothing_image in test_image_paths:
  file_path = f'{IMAGES_PATH}/{clothing_image}'
  im = cv2.imread(file_path)
  outputs = predictor(im)
  v = Visualizer(
    im[:, :, ::-1],
    metadata=statement_metadata,
    scale=1.,
    instance_mode=ColorMode.IMAGE
  )
  instances = outputs["instances"].to("cpu")
  instances.remove('pred_masks')
  v = v.draw_instance_predictions(instances)
  result = v.get_image()[:, :, ::-1]
  file_name = ntpath.basename(clothing_image)
  write_res = cv2.imwrite(f'annotated_results/{file_name}', result)

eaef04c00ad6f46682d79e21682c449c.png

・ 終わり ・

幸せな生活

c941539f67c96091161bb2db5bd893b7.png

この記事は学習とコミュニケーションのみを目的としています。侵害がある場合は、作成者に連絡して削除してください。

おすすめ

転載: blog.csdn.net/weixin_38739735/article/details/132353452