ユーザ用ツール

サイト用ツール


opencv_facedetect

差分

このページの2つのバージョン間の差分を表示します。

この比較画面へのリンク

次のリビジョン
前のリビジョン
opencv_facedetect [2026/07/08 15:03] – 作成 bokupiopencv_facedetect [2026/07/09 14:25] (現在) bokupi
行 1: 行 1:
-====== 顔検出してサムネイル画像作成ツール ======+====== 顔検出によるサムネイル画像作成ツール ======
  
 自作のイラストファイルから顔検出して、その顔から160x160のサムネイル画像を自動で作成するツールです。 自作のイラストファイルから顔検出して、その顔から160x160のサムネイル画像を自動で作成するツールです。
行 31: 行 31:
 lbpcascade_animeface.xml は自分で入手が必要です。 lbpcascade_animeface.xml は自分で入手が必要です。
 今回は下記のURLからダウンロードしました。 今回は下記のURLからダウンロードしました。
-https://github.com/nagadomi/lbpcascade_animeface+  * https://github.com/nagadomi/lbpcascade_animeface
  
 なおOpenCV5からは、基本モジュールにHaar Cascadeは含まれず、 なおOpenCV5からは、基本モジュールにHaar Cascadeは含まれず、
行 113: 行 113:
  
 モデルは下記からダウンロードしました。どちらも同じファイルっぽいですね。 モデルは下記からダウンロードしました。どちらも同じファイルっぽいですね。
-https://huggingface.co/YZBPXX/anime_face_detect/tree/main +  * https://huggingface.co/YZBPXX/anime_face_detect/tree/main 
-https://huggingface.co/deepghs/anime_face_detection/tree/main/face_detect_v1.4_s+  https://huggingface.co/deepghs/anime_face_detection/tree/main/face_detect_v1.4_s
  
 [[https://huggingface.co/|Hugging Face]]から"anime"などのキーワードで探すのが良いと思います。 [[https://huggingface.co/|Hugging Face]]から"anime"などのキーワードで探すのが良いと思います。
行 258: 行 258:
  
 </code> </code>
 +
 +====== 精度比較 ======
 +
 +きちんと比較したわけではありませんが、
 +Haar Cascadeでは想定通りに顔検出できなかった「瞑目」イラストについて、
 +DNNでは期待通り顔検出していたので、DNNの方が精度が高そうだと思っています。
 +
 +====== ツールとして整備したスクリプト ======
 +
 +指定した入力ディレクトリ配下にあるファイルを対象に、
 +サムネイルを作成し、指定した出力ディレクトリ配下に出力します。
 +
 +<code python>
 +import cv2
 +import numpy as np
 +from pathlib import Path
 +
 +def crop_to_square(image, box):
 +    """
 +    検出されたバウンディングボックスを長辺に合わせた正方形に拡張し、切り出す関数
 +    (画像外にはみ出る場合は黒色でパディング)
 +    """
 +    h_img, w_img = image.shape[:2]
 +    x, y, w, h = box
 +    
 +    # 中心座標と長辺の長さを計算
 +    cx = x + w / 2
 +    cy = y + h / 2
 +    side = max(w, h)
 +    
 +    # 正方形の開始座標と終了座標
 +    x1 = int(cx - side / 2)
 +    y1 = int(cy - side / 2)
 +    x2 = int(x1 + side)
 +    y2 = int(y1 + side)
 +    
 +    # 画像の範囲内・範囲外の座標を計算(パディング用)
 +    pad_x1 = max(0, -x1)
 +    pad_y1 = max(0, -y1)
 +    pad_x2 = max(0, x2 - w_img)
 +    pad_y2 = max(0, y2 - h_img)
 +    
 +    # 実際の画像からクロップできる範囲
 +    crop_x1 = max(0, x1)
 +    crop_y1 = max(0, y1)
 +    crop_x2 = min(w_img, x2)
 +    crop_y2 = min(h_img, y2)
 +    
 +    # クロップの実行
 +    cropped = image[crop_y1:crop_y2, crop_x1:crop_x2]
 +    
 +    # 範囲外にはみ出ていた場合は黒(0)でパディング
 +    if pad_x1 > 0 or pad_y1 > 0 or pad_x2 > 0 or pad_y2 > 0:
 +        cropped = cv2.copyMakeBorder(
 +            cropped, pad_y1, pad_y2, pad_x1, pad_x2, 
 +            cv2.BORDER_CONSTANT, value=[0, 0, 0]
 +        )
 +        
 +    return cropped
 +
 +def detect_and_save_faces(image_path, save_path, model_path, conf_threshold=0.4, nms_threshold=0.4):
 +    # 画像の読み込み
 +    img = cv2.imread(image_path)
 +    if img is None:
 +        print(f"Error: 画像ファイルが見つかりません: {image_path}")
 +        return
 +        
 +    h_img, w_img = img.shape[:2]
 +    
 +    # ONNXモデルの読み込み (OpenCV 5 DNN)
 +    net = cv2.dnn.readNetFromONNX(model_path)
 +    
 +    # モデルの推奨入力サイズ(一般的にYOLOv8系は640x640)
 +    input_size = (640, 640)
 +    
 +    # 変更前
 +    # blob = cv2.dnn.blobFromImage(img, 1/255.0, input_size, swapRB=False, crop=False)
 +    # 変更後:ImageNetの平均値(Mean)と標準偏差(Std)を適用
 +    # 1/255.0 に 加えて 標準偏差(0.229) で割るための係数を計算
 +    scalefactor = 1.0 / (255.0 * 0.229) 
 +    mean = [0.485 * 255, 0.456 * 255, 0.406 * 255]
 +
 +    blob = cv2.dnn.blobFromImage(
 +        img, 
 +        scalefactor=scalefactor, 
 +        size=input_size, 
 +        mean=mean, 
 +        swapRB=True, 
 +        crop=False
 +    )
 +    net.setInput(blob)
 +    
 +    # 推論の実行
 +    outputs = net.forward()
 +    
 +    # YOLOv8の出力形状は [1, 5, 8400] (5項目: x_center, y_center, width, height, confidence)
 +    # 扱いやすいように転置
 +    predictions = np.squeeze(outputs).T
 +    
 +    boxes = []
 +    confidences = []
 +    
 +    # スケール比率の計算
 +    x_factor = w_img / input_size[0]
 +    y_factor = h_img / input_size[1]
 +    
 +    for pred in predictions:
 +        # スコア(顔である確率)を取得
 +        confidence = pred[4]
 +        if confidence >= conf_threshold:
 +            # 座標を元の画像サイズに復元
 +            cx, cy, w, h = pred[0], pred[1], pred[2], pred[3]
 +            
 +            x = int((cx - w / 2) * x_factor)
 +            y = int((cy - h / 2) * y_factor)
 +            w = int(w * x_factor)
 +            h = int(h * y_factor)
 +            
 +            boxes.append([x, y, w, h])
 +            confidences.append(float(confidence))
 +            
 +    # 重複するボックスを非大値抑制(NMS)で排除
 +    indices = cv2.dnn.NMSBoxes(boxes, confidences, conf_threshold, nms_threshold)
 +    
 +    if len(indices) == 0:
 +        print("顔は検出されませんでした。")
 +        return
 +
 +    # 最もスコアが高い検出範囲を処理する。
 +    # cv2.dnn.NMSBoxes の出力はスコアの降順(高い順)にソートされているため、
 +    # フラット化した配列の先頭([0])が最高スコアのインデックスになる。
 +    best_idx = indices.flatten()[0]
 +    box = boxes[best_idx]
 +    
 +    # 1. 長辺合わせの正方形で切り出し
 +    square_crop = crop_to_square(img, box)
 +    
 +    # 2. 160x160にリサイズ
 +    resized_face = cv2.resize(square_crop, (160, 160), interpolation=cv2.INTER_AREA)
 +    
 +    cv2.imwrite(save_path, resized_face)
 +    print(f"Saved: {save_path} (Best Conf: {confidences[best_idx]:.2f})")
 +
 +    # 一定以上のスコアを持つ検出範囲を全て処理したい場合
 +    # 複数のキャラクターがいるケースだと意味がある
 +    if False:
 +        # 検出した顔の処理と保存
 +        for i, idx in enumerate(indices.flatten()):
 +            box = boxes[idx]
 +            
 +            # 1. 長辺合わせの正方形で切り出し
 +            square_crop = crop_to_square(img, box)
 +            
 +            # 2. 160x160にリサイズ
 +            resized_face = cv2.resize(square_crop, (160, 160), interpolation=cv2.INTER_AREA)
 +            
 +            # 3. ファイル保存(※ファイル名の設定は要修正。このままだと同じファイル名で上書きする)
 +            cv2.imwrite(save_path, resized_face)
 +            print(f"Saved: {save_path} (Conf: {confidences[idx]:.2f})")
 +
 +# --- 実行例 ---
 +if __name__ == "__main__":
 +    # HuggingFaceからダウンロードしたONNXファイルのパスを指定してください
 +    MODEL_PATH = "model.onnx" 
 +    input_dir = Path("input")
 +    output_dir = Path("output")
 +
 +    # ディレクトリ内のファイルを1つずつループ処理
 +    # .iterdir() はディレクトリ内のファイルやフォルダをすべて列挙します
 +    for file_path in input_dir.iterdir():
 +        
 +        # フォルダ(ディレクトリ)は除外して、ファイルのみを処理する場合
 +        if file_path.is_file():
 +            print(f"処理中のファイル: {file_path.name}")
 +            
 +            # 出力先ファイルパスを作成する
 +            save_path = output_dir / f"{file_path.stem}-s{file_path.suffix}"
 +            print(f"保存先パス: {save_path}")
 +            detect_and_save_faces(file_path, save_path, MODEL_PATH)
 +</code>
 +
 +====== 注釈 ======
 +
 +本ページで紹介しているコードは、Geminiに生成させたものをベースに修正を加えたものになっています。
 +
opencv_facedetect.1783523015.txt.gz · 最終更新: by bokupi

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki