人間の顔と肌の検出・セグメンテーション・領域抽出(ソースコードと実行結果)she

Python開発環境,ライブラリ類

ここでは、最低限の事前準備について説明する。機械学習や深層学習を行う場合は、NVIDIA CUDA、Visual Studio、Cursorなどを追加でインストールすると便利である。これらについては別ページ https://www.kkaneko.jp/cc/dev/aiassist.htmlで詳しく解説しているので、必要に応じて参照してください。

Python 3.12 のインストール

インストール済みの場合は実行不要。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要である。

REM Python をシステム領域にインストール
winget install --scope machine --id Python.Python.3.12 -e --silent
REM Python のパス設定
set "PYTHON_PATH=C:\Program Files\Python312"
set "PYTHON_SCRIPTS_PATH=C:\Program Files\Python312\Scripts"
echo "%PATH%" | find /i "%PYTHON_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_PATH%" /M >nul
echo "%PATH%" | find /i "%PYTHON_SCRIPTS_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_SCRIPTS_PATH%" /M >nul

関連する外部ページ

Python の公式ページ: https://www.python.org/

AI エディタ Windsurf のインストール

Pythonプログラムの編集・実行には、AI エディタの利用を推奨する。ここでは,Windsurfのインストールを説明する。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行して、Windsurfをシステム全体にインストールする。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要となる。

winget install --scope machine Codeium.Windsurf -e --silent

関連する外部ページ

Windsurf の公式ページ: https://windsurf.com/

必要なライブラリのインストール

コマンドプロンプトを管理者として実行(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する


pip install opencv-python numpy scikit-learn colorspacious

人間の顔と肌の検出・セグメンテーション・領域抽出プログラム

ソースコード


# プログラム名: 複数色空間統合による肌検出プログラム
# 特徴技術名: 複数色空間統合による適応的肌検出(Multi-Color Space Adaptive Skin Detection)
# 出典:
#   - Rahmat et al. (2016). "Skin Color Segmentation Using Multi-Color Space Threshold." ICCOINS 2016
#   - Alyoubi et al. (2025). "Colors Matter: AI-Driven Exploration of Human Feature Colors." arXiv:2505.14931
# 特徴機能: HSV、YCbCr、LAB色空間を統合し、適応的閾値処理とCIEDE2000色差メトリックを使用した照明条件・肌色変動に堅牢な肌検出システム
# 学習済みモデル: 使用しません(色空間統合とクラスタリングによる手法)
# 方式設計:
#   関連利用技術:
#     - OpenCV:画像処理、色空間変換、モルフォロジー処理、K-meansクラスタリング
#     - NumPy:配列操作、数値計算
#     - scikit-learn:K-meansクラスタリング
#     - colorspacious:CIEDE2000色差計算
#     - Tkinter:ファイル選択ダイアログ
#     - urllib:サンプル画像のダウンロード
#   入力と出力:
#     - 入力: 画像ファイル,カメラ(ユーザは「0:画像ファイル,1:カメラ,2:サンプル画像」のメニューで選択)
#     - 出力: 処理結果をOpenCV画面にリアルタイム表示、結果をresult.txtファイルに保存
#   処理手順:
#     1. ガウシアンブラー適用によるノイズ除去
#     2. 複数色空間(HSV、YCbCr、LAB)への変換
#     3. YCbCr色空間の統合使用 - Rahmat et al. (2016)による輝度と色差分離手法
#     4. 正規化RGB色空間の独立使用 - 従来の肌検出研究による照明不変性向上手法
#     5. 各色空間での適応的閾値処理による肌領域候補抽出
#     6. 複数色空間の論理演算統合 - Rahmat et al. (2016)のMulti-Color Space Threshold手法
#     7. 論理演算による統合肌マスク生成
#     8. K-meansクラスタリングによる肌色の動的抽出
#     9. CIEDE2000メトリックによる色差評価
#     10. 信頼度評価システム - 独自実装による検出結果信頼性評価
#     11. モルフォロジー処理による最終肌領域の精製
#   前処理、後処理:
#     - 前処理: ガウシアンブラー、色空間変換、適応的閾値処理
#     - 後処理: K-meansクラスタリング、モルフォロジー処理、CIEDE2000評価
#   追加処理:
#     - 複数色空間統合による堅牢性向上
#     - 適応的閾値処理で照明条件変動に対応
#     - K-meansクラスタリングによる動的肌色抽出
#     - CIEDE2000による知覚的色差評価
#     - YCbCr色空間統合処理 - Rahmat et al. (2016)
#     - 正規化RGB独立処理 - 従来研究
#     - 複数色空間論理演算統合 - Rahmat et al. (2016)
#     - 信頼度評価システム - 独自実装
#   調整を必要とする設定値:
#     - blur_kernel_size: ガウシアンブラーのカーネルサイズ (5, 5)
#     - hsv_skin_range: HSV色空間の肌色範囲
#     - ycbcr_skin_range: YCbCr色空間の肌色範囲 - Rahmat et al. (2016)
#     - lab_skin_range: LAB色空間の肌色範囲
#     - norm_rgb_threshold: 正規化RGB閾値 - 従来研究
# 将来方策:
#   - 機械学習による動的パラメータ調整
#   - 深層学習モデルとの統合
#   - リアルタイム処理の最適化
#   - 多様な肌色データセットでの学習
# その他の重要事項:
#   - 照明条件変動への対応が改善
#   - 異なる肌色での検出精度向上
#   - 計算コストの増加に注意
#   - 複数色空間統合技術採用 - Rahmat et al. (2016)
#   - 信頼度評価機能実装 - 独自実装
# 前準備: pip install opencv-python numpy scikit-learn colorspacious ultralytics

import cv2
import numpy as np
import tkinter as tk
from tkinter import filedialog
import urllib.request
import time
from datetime import datetime
from sklearn.cluster import KMeans
import colorspacious

def image_processing(img):
    """静止画の肌検出処理"""
    # ガウシアンブラー適用
    blurred = cv2.GaussianBlur(img, (5, 5), 0)

    # 複数色空間への変換
    hsv = cv2.cvtColor(blurred, cv2.COLOR_BGR2HSV)
    ycbcr = cv2.cvtColor(blurred, cv2.COLOR_BGR2YCrCb)
    lab = cv2.cvtColor(blurred, cv2.COLOR_BGR2LAB)
    rgb = cv2.cvtColor(blurred, cv2.COLOR_BGR2RGB)

    # 正規化RGB計算
    rgb_sum = rgb[:,:,0].astype(np.float64) + rgb[:,:,1].astype(np.float64) + rgb[:,:,2].astype(np.float64)
    rgb_sum = np.where(rgb_sum == 0, 1.0, rgb_sum)
    norm_r = rgb[:,:,0].astype(np.float64) / rgb_sum
    norm_g = rgb[:,:,1].astype(np.float64) / rgb_sum
    norm_b = rgb[:,:,2].astype(np.float64) / rgb_sum

    # HSV色空間での肌検出
    hsv_mask = cv2.inRange(hsv, np.array([0, 20, 60]), np.array([25, 170, 255]))

    # YCbCr色空間での肌検出
    ycbcr_mask = cv2.inRange(ycbcr, np.array([80, 110, 125]), np.array([255, 140, 145]))

    # LAB色空間での肌検出
    lab_mask = cv2.inRange(lab, np.array([30, 130, 115]), np.array([200, 145, 135]))

    # 正規化RGB条件
    condition1 = norm_r > 0.28
    condition2 = norm_g > 0.28
    condition3 = norm_b > 0.26
    condition4 = norm_r > norm_g
    condition5 = norm_r > norm_b
    condition6 = (norm_r - norm_g) > 0.01
    condition7 = norm_r / (norm_g + 0.001) > 1.05

    condition = condition1 & condition2 & condition3 & condition4 & condition5 & condition6 & condition7
    norm_rgb_mask = np.zeros(img.shape[:2], dtype=np.uint8)
    norm_rgb_mask[condition] = 255

    # 複数色空間の統合
    mask_count = np.zeros(img.shape[:2], dtype=np.uint8)
    mask_count += (hsv_mask > 0).astype(np.uint8)
    mask_count += (ycbcr_mask > 0).astype(np.uint8)
    mask_count += (lab_mask > 0).astype(np.uint8)
    mask_count += (norm_rgb_mask > 0).astype(np.uint8)

    # 2つ以上の色空間で肌と判定された領域のみを採用
    combined_mask = np.zeros(img.shape[:2], dtype=np.uint8)
    combined_mask[mask_count >= 2] = 255

    # K-meansクラスタリング
    skin_pixels = img[combined_mask > 0]
    confidence = 0.1

    if len(skin_pixels) > 100:
        lab_pixels = cv2.cvtColor(skin_pixels.reshape(-1, 1, 3), cv2.COLOR_BGR2LAB)
        lab_pixels = lab_pixels.reshape(-1, 3)

        # OpenCV LAB値をCIE LAB標準範囲に変換
        lab_pixels_cie = np.zeros_like(lab_pixels, dtype=np.float64)
        lab_pixels_cie[:, 0] = lab_pixels[:, 0] * 100.0 / 255.0  # L: [0-255] -> [0-100]
        lab_pixels_cie[:, 1] = lab_pixels[:, 1] - 128.0  # a: [0-255] -> [-128-127]
        lab_pixels_cie[:, 2] = lab_pixels[:, 2] - 128.0  # b: [0-255] -> [-128-127]

        n_clusters = min(3, len(skin_pixels) // 50)
        if n_clusters > 0:
            kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10)
            cluster_labels = kmeans.fit_predict(lab_pixels_cie)

            cluster_centers = kmeans.cluster_centers_
            largest_cluster = np.argmax(np.bincount(cluster_labels))
            dominant_color_lab = cluster_centers[largest_cluster]

            # 参照肌色LAB値(CIE LAB標準範囲)
            reference_skin_lab = np.array([65, 10, 15])  # 典型的な肌色のCIE LAB値
            delta_e = colorspacious.deltaE(reference_skin_lab, dominant_color_lab, input_space='CAM02-UCS')
            confidence = max(0, 1 - delta_e / 50)

    # モルフォロジー処理
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
    combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_OPEN, kernel)
    combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_CLOSE, kernel)

    # 小さな領域の除去
    contours, _ = cv2.findContours(combined_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    for contour in contours:
        if cv2.contourArea(contour) < 500:
            cv2.fillPoly(combined_mask, [contour], 0)

    # 最終結果
    result = cv2.bitwise_and(img, img, mask=combined_mask)
    skin_pixels_count = cv2.countNonZero(combined_mask)
    total_pixels = img.shape[0] * img.shape[1]
    skin_percentage = (skin_pixels_count / total_pixels) * 100

    # 結果の可視化
    combined = np.hstack([img, result])
    cv2.putText(combined, f'Skin: {skin_percentage:.1f}%', (10, 30),
                cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
    cv2.putText(combined, f'Confidence: {confidence:.2f}', (10, 60),
                cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)

    result_text = f'肌検出領域: {skin_percentage:.1f}%, 信頼度: {confidence:.2f}'

    return combined, result_text, time.time()

def process_and_display_images(image_sources, source_type):
    display_index = 1
    for source in image_sources:
        img = cv2.imread(source) if source_type == 'file' else source
        if img is None:
            continue
        cv2.imshow(f'Image_{display_index}', img)
        processed_img, result, current_time = image_processing(img)
        cv2.imshow(f'肌検出結果_{display_index}', processed_img)
        print(datetime.fromtimestamp(current_time).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3], result)
        results_log.append(result)
        display_index += 1

# メイン処理
print('\n=== 複数色空間統合による肌検出プログラム ===')
print('概要: HSV、YCbCr、LAB、正規化RGBの4つの色空間を統合して肌領域を検出')
print('\n操作方法:')
print('0: 画像ファイル')
print('1: カメラ(スペースキーで撮影、qキーで終了)')
print('2: サンプル画像')

choice = input('\n選択: ')
results_log = []

try:
    if choice == '0':
        root = tk.Tk()
        root.withdraw()
        if not (paths := filedialog.askopenfilenames()):
            exit()
        process_and_display_images(paths, 'file')
        cv2.waitKey(0)

    elif choice == '1':
        cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
        if not cap.isOpened():
            cap = cv2.VideoCapture(0)
        cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)

        print('\n=== カメラ起動 ===')
        print('スペースキー: 撮影して肌検出')
        print('qキー: 終了')

        try:
            while True:
                ret, frame = cap.read()
                if not ret:
                    break
                cv2.imshow('Camera', frame)
                key = cv2.waitKey(1) & 0xFF
                if key == ord(' '):
                    processed_img, result, current_time = image_processing(frame)
                    cv2.imshow('肌検出結果', processed_img)
                    print(datetime.fromtimestamp(current_time).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3], result)
                    results_log.append(result)
                elif key == ord('q'):
                    break
        finally:
            cap.release()

    else:
        urls = [
            "https://raw.githubusercontent.com/opencv/opencv/master/samples/data/fruits.jpg",
            "https://raw.githubusercontent.com/opencv/opencv/master/samples/data/messi5.jpg",
            "https://raw.githubusercontent.com/opencv/opencv/master/samples/data/aero3.jpg",
            "https://upload.wikimedia.org/wikipedia/commons/3/3a/Cat03.jpg"
        ]
        downloaded_files = []
        for i, url in enumerate(urls):
            try:
                urllib.request.urlretrieve(url, f"sample_{i}.jpg")
                downloaded_files.append(f"sample_{i}.jpg")
            except:
                print(f"画像のダウンロードに失敗しました: {url}")
        process_and_display_images(downloaded_files, 'file')
        cv2.waitKey(0)

finally:
    print('\n=== プログラム終了 ===')
    cv2.destroyAllWindows()
    if results_log:
        with open('result.txt', 'w', encoding='utf-8') as f:
            f.write('=== 結果 ===\n')
            f.write('\n')
            f.write('\n'.join(results_log))
        print(f'\n処理結果をresult.txtに保存しました')