メインコンテンツへスキップ
見出し画像

写真から主要色を抽出!Python画像解析で作るカラーパレット生成アプリ

    「インスタの投稿に統一感を出したいけど、配色センスがない...」
    「お気に入りの写真の色合いでWebサイトを作りたい!」
    「プログラミング学習したけど、何か実用的なものを作ってみたい」

    今回は、そんな悩みを解決する画像解析アプリをPythonで作ります!

    完成すれば、こんなことができるようになります。

    • 📷 写真をアップロードするだけで主要色を自動抽出

    • 🎨 美しいカラーパレットを生成

    • 💻 デザイン作業で実際に使えるHEXコード出力

    • 📊 各色の占有率も分析可能

    しかもGoogle Colabを使うので環境構築不要!今すぐ始められます。


    🎯 今日作るもの

    写真から主要な色を抽出して、デザイナーが使うようなカラーパレットを自動生成するアプリです。

    例えば夕焼けの写真なら → オレンジ、ピンク、紫の美しいパレット
    カフェの写真なら → ブラウン、ベージュ、グリーンの落ち着いたパレット

    こんな風に、写真の「雰囲気」を色で表現できるツールを作ります。


    🛠️ 環境セットアップ

    まずはGoogle Colabを開いて、必要なライブラリをインストールしましょう。

    Google Drive → 「新規」→ 「その他」→ 「Google Colaboratory」

    画像
    Google Driveから誰でも無料で作成できます!

    以下のコードを順番にコードブロックにコピペするだけ!
    (初めての人は下の画像を参照して真似する!)

    # 必要なライブラリのインストール
    !pip install pillow matplotlib numpy
    
    # ライブラリのインポート
    from PIL import Image
    import matplotlib.pyplot as plt
    import numpy as np
    from collections import Counter
    import io
    import requests
    
    # 日本語フォント設定(グラフの文字化け防止)
    plt.rcParams['font.family'] = 'DejaVu Sans'
    
    print("✅ セットアップ完了!")
    
    画像
    コードブロックに上記のコードをそのままコピペして「▶️」ボタンをクリックするだけ!

    📚 基礎知識:デジタル色彩の世界

    プログラムで色を扱う前に、デジタル画像の色について理解しておきましょう。

    RGB値って何?

    デジタル画像の色はRGB(Red, Green, Blue)で表現されます。 各色は0〜255の値を持ち、組み合わせることで約1677万色を作れます。

    # RGB値の例を視覚的に確認
    colors_info = [
        ("赤色", (255, 0, 0)),
        ("緑色", (0, 255, 0)), 
        ("青色", (0, 0, 255)),
        ("黄色", (255, 255, 0)),
        ("紫色", (128, 0, 128)),
        ("白色", (255, 255, 255)),
        ("黒色", (0, 0, 0))
    ]
    
    fig, axes = plt.subplots(1, len(colors_info), figsize=(14, 2))
    
    for i, (name, rgb) in enumerate(colors_info):
        # 50x50の色見本を作成
        color_square = np.full((50, 50, 3), rgb, dtype=np.uint8)
        axes[i].imshow(color_square)
        axes[i].set_title(f"{name}\nRGB{rgb}", fontsize=10)
        axes[i].axis('off')
    
    plt.tight_layout()
    plt.suptitle("RGB色見本", fontsize=16, y=1.08)
    plt.show()
    
    画像
    上記のコードを実行するとこんな感じのカラーブロックが出力される

    🖼️ Step1:画像を読み込んでみよう

    まずは画像の基本的な扱い方から学びましょう。
    今回はサンプル画像を作って練習します。

    # カラフルなサンプル画像を生成(実際の写真の代わり)
    # 実際は自分の写真をアップロードして使います
    np.random.seed(42)  # 結果を再現可能にする
    
    # 虹色のようなグラデーション画像を作成
    width, height = 200, 200
    sample_image = np.zeros((height, width, 3), dtype=np.uint8)
    
    for y in range(height):
        for x in range(width):
            # 位置に応じて色を変化
            r = int(255 * (x / width))
            g = int(255 * (y / height))
            b = int(255 * ((x + y) / (width + height)))
            sample_image[y, x] = [r, g, b]
    
    # NumPy配列からPIL画像に変換
    sample_pil = Image.fromarray(sample_image)
    
    # 画像情報を表示
    print(f"📸 画像サイズ: {sample_pil.size} pixels")  # (幅, 高さ)
    print(f"🎨 画像モード: {sample_pil.mode}")  # RGB, RGBA など
    print(f"📊 合計ピクセル数: {sample_pil.size[0] * sample_pil.size[1]:,} pixels")
    
    画像
    コードだけで画像を生成すると・・・👇続き
    # 画像を表示
    plt.figure(figsize=(8, 6))
    plt.imshow(sample_pil)
    plt.title("サンプル画像(虹色グラデーション)", fontsize=14)
    plt.axis('off')
    plt.show()
    
    print("🌈 美しいグラデーション画像ができました!")
    
    画像
    一つ前のコードによって生成された画像がこちら

    コードだけで画像を生成することができた!


    🎨 Step2:最も多い色を見つけよう

    まずは画像の中で「最も多く使われている色」を見つける方法を学びます。

    # より色の領域がはっきりした画像を生成
    def create_realistic_sample_image():
        """
        色抽出に適したサンプル画像を作成
        """
        width, height = 200, 200
        sample_image = np.zeros((height, width, 3), dtype=np.uint8)
        
        # 5つの色領域を作成(花や風景を想定)
        regions = [
            # (開始x, 終了x, 開始y, 終了y, RGB色)
            (0, 100, 0, 100, [135, 206, 235]),      # 空色(左上)
            (100, 200, 0, 100, [34, 139, 34]),      # 緑(右上)
            (0, 80, 100, 200, [139, 69, 19]),       # 茶色(左下)
            (80, 150, 100, 200, [255, 192, 203]),   # ピンク(中央下)
            (150, 200, 100, 200, [255, 215, 0])     # 黄色(右下)
        ]
        
        for x1, x2, y1, y2, color in regions:
            sample_image[y1:y2, x1:x2] = color
            
            # 各領域に少しノイズを追加(よりリアルに)
            noise = np.random.randint(-10, 10, (y2-y1, x2-x1, 3))
            sample_image[y1:y2, x1:x2] = np.clip(
                sample_image[y1:y2, x1:x2].astype(int) + noise, 0, 255
            ).astype(np.uint8)
        
        return Image.fromarray(sample_image)
    
    # 新しいサンプル画像を生成
    sample_pil = create_realistic_sample_image()
    
    # 画像を確認
    plt.figure(figsize=(8, 6))
    plt.imshow(sample_pil)
    plt.title("改良版サンプル画像(5色の領域)", fontsize=14)
    plt.axis('off')
    plt.show()
    
    print("🎨 色の領域がはっきりした画像を生成しました!")
    画像
    新しい画像を用意
    def quantize_colors(image, levels=16):
        """
        色を量子化して似た色をまとめる
        """
        rgb_array = np.array(image)
        
        # 各色成分を指定したレベル数に量子化
        quantized = np.floor(rgb_array / (256 / levels)) * (256 / levels)
        quantized = np.clip(quantized, 0, 255).astype(np.uint8)
        
        return Image.fromarray(quantized)
    
    def get_dominant_color_quantized(image, quantize_levels=16):
        """
        量子化を使った色抽出
        """
        # 色を量子化
        quantized_image = quantize_colors(image, quantize_levels)
        
        print(f"🎨 色を{quantize_levels}レベルに量子化しました")
        
        # 量子化した画像で色抽出
        rgb_array = np.array(quantized_image)
        pixels = rgb_array.reshape(-1, 3)
        
        pixel_tuples = [tuple(map(int, pixel)) for pixel in pixels]
        color_counter = Counter(pixel_tuples)
        
        print(f"📊 量子化後の色の種類: {len(color_counter):,}")
        
        most_common = color_counter.most_common(10)
        dominant_color = most_common[0][0]
        
        print(f"✨ 最頻値色: RGB{dominant_color}")
        
        # 詳細情報
        total_pixels = len(pixels)
        print(f"🔍 Top 5色:")
        for i, (color, count) in enumerate(most_common[:5]):
            percentage = (count / total_pixels) * 100
            print(f"  第{i+1}位: RGB{color} - {count:,}回 ({percentage:.1f}%)")
        
        return dominant_color, most_common[:5], quantized_image
    
    # 量子化版で実行
    dominant_q, top_colors_q, quantized_img = get_dominant_color_quantized(sample_pil, quantize_levels=16)
    
    # 結果比較表示
    plt.figure(figsize=(16, 8))
    
    # 元画像
    plt.subplot(2, 3, 1)
    plt.imshow(sample_pil)
    plt.title("元画像(ノイズあり)", fontsize=12)
    plt.axis('off')
    
    # 量子化画像
    plt.subplot(2, 3, 2)
    plt.imshow(quantized_img)
    plt.title("量子化画像", fontsize=12)
    plt.axis('off')
    
    # 量子化版の最頻値色
    plt.subplot(2, 3, 3)
    color_square_q = np.full((100, 100, 3), dominant_q, dtype=np.uint8)
    plt.imshow(color_square_q)
    plt.title(f"量子化版最頻値色\nRGB{dominant_q}", fontsize=12)
    plt.axis('off')
    
    # クリーン画像(比較用)
    sample_pil_clean = create_clean_sample_image()
    dominant_clean, top_colors_clean = get_dominant_color_simple(sample_pil_clean)
    
    plt.subplot(2, 3, 4)
    plt.imshow(sample_pil_clean)
    plt.title("クリーン画像(ノイズなし)", fontsize=12)
    plt.axis('off')
    
    # クリーン版バーグラフ
    plt.subplot(2, 3, 5)
    colors_clean = [color[0] for color in top_colors_clean[:5]]
    counts_clean = [color[1] for color in top_colors_clean[:5]]
    colors_clean_norm = [[c/255.0 for c in color] for color in colors_clean]
    
    bars_clean = plt.bar(range(len(colors_clean)), counts_clean, color=colors_clean_norm)
    plt.title("クリーン画像 Top5", fontsize=12)
    plt.xlabel("順位")
    plt.ylabel("出現回数")
    
    # 量子化版バーグラフ
    plt.subplot(2, 3, 6)
    colors_q = [color[0] for color in top_colors_q]
    counts_q = [color[1] for color in top_colors_q]
    colors_q_norm = [[c/255.0 for c in color] for color in colors_q]
    
    bars_q = plt.bar(range(len(colors_q)), counts_q, color=colors_q_norm)
    plt.title("量子化版 Top5", fontsize=12)
    plt.xlabel("順位")
    plt.ylabel("出現回数")
    
    plt.tight_layout()
    plt.show()
    
    print("\n🎉 両方の手法で正常な結果が得られました!")
    print("💡 実際の写真では量子化手法がより有効です。")
    画像

    ノイズあり・ノイズなしで順位が変わっていることが見て取れます。
    一見、、水色や緑色などが多いように見えますが、ピクセル単位で見ると実は黄色が一番多いということなんです。


    📊 Step3:色の分布を詳しく調べよう

    画像にはどんな色がどのくらい含まれているのか、もう少し詳しく調べてみましょう。

    def analyze_color_distribution(image, top_n=10):
        """
        画像の色分布を詳しく分析する関数
        """
        rgb_array = np.array(image)
        pixels = rgb_array.reshape(-1, 3)
        
        # 各色要素の統計を計算
        r_values = pixels[:, 0]
        g_values = pixels[:, 1] 
        b_values = pixels[:, 2]
        
        print("🔴 赤色成分の統計:")
        print(f"  平均: {np.mean(r_values):.1f}")
        print(f"  標準偏差: {np.std(r_values):.1f}")
        print(f"  範囲: {np.min(r_values)} - {np.max(r_values)}")
        
        print("\n🟢 緑色成分の統計:")
        print(f"  平均: {np.mean(g_values):.1f}")
        print(f"  標準偏差: {np.std(g_values):.1f}")
        print(f"  範囲: {np.min(g_values)} - {np.max(g_values)}")
        
        print("\n🔵 青色成分の統計:")
        print(f"  平均: {np.mean(b_values):.1f}")
        print(f"  標準偏差: {np.std(b_values):.1f}")
        print(f"  範囲: {np.min(b_values)} - {np.max(b_values)}")
        
        # 色分布のヒストグラム
        plt.figure(figsize=(15, 4))
        
        plt.subplot(1, 3, 1)
        plt.hist(r_values, bins=50, alpha=0.7, color='red')
        plt.title('赤色成分の分布')
        plt.xlabel('RGB値')
        plt.ylabel('頻度')
        
        plt.subplot(1, 3, 2)
        plt.hist(g_values, bins=50, alpha=0.7, color='green')
        plt.title('緑色成分の分布')
        plt.xlabel('RGB値')
        plt.ylabel('頻度')
        
        plt.subplot(1, 3, 3)
        plt.hist(b_values, bins=50, alpha=0.7, color='blue')
        plt.title('青色成分の分布')
        plt.xlabel('RGB値')
        plt.ylabel('頻度')
        
        plt.tight_layout()
        plt.show()
    
    # 分析実行
    analyze_color_distribution(sample_pil)
    
    画像
    🔴 赤色成分の統計: 平均: 145.3 標準偏差: 81.4 範囲: 24 - 255
    🟢 緑色成分の統計: 平均: 160.0 標準偏差: 53.7 範囲: 59 - 224
    🔵 青色成分の統計: 平均: 106.4 標準偏差: 99.9 範囲: 0 - 244

    🎯 平均値(Average)から分かること

    赤: 145.3  緑: 160.0  青: 106.4
    • 緑 > 赤 > 青の順 → 画像全体が緑がかった色調

    • 0-255の中央は127.5なので、緑と赤がやや明るく、青が暗め

    • 結論: 自然系(草原、森林)や暖かい色調の画像

    📈 標準偏差(Standard Deviation)から分かること

    青: 99.9  赤: 81.4  緑: 53.7
    • 青のばらつきが最大 → 濃い青から薄い青まで幅広く存在

    • 緑のばらつきが最小 → 緑色が比較的均一

    • 結論: 空(青の濃淡)と植物(安定した緑)が含まれる風景的画像

    📏 範囲(Range)から分かること

    赤: 24-255 (範囲231)  青: 0-244 (範囲244)  緑: 59-224 (範囲165)
    • 青と赤が全範囲近く → コントラストが強い

    • 緑は中程度の範囲 → 極端に濃い/薄い緑は少ない


    🌈 Step4:簡単なカラーパレット生成

    ここまでで学んだ技術を使って、簡単なカラーパレットを作ってみましょう。

    # カラーパレット表示
    def display_correct_palette(colors_list, image_title="修正版"):
        """
        カラーパレットを表示
        """
        # パレット画像作成
        palette_height = 100
        palette_width = 500
        n_colors = len(colors_list)
        color_width = palette_width // n_colors
        
        palette_img = np.zeros((palette_height, palette_width, 3), dtype=np.uint8)
        
        print(f"🎨 {image_title}のカラーパレット作成:")
        
        for i, (color, count) in enumerate(colors_list):
            start_x = i * color_width
            end_x = (i + 1) * color_width
            if i == n_colors - 1:
                end_x = palette_width
            
            # 色を配置
            palette_img[:, start_x:end_x, 0] = color[0]  # R
            palette_img[:, start_x:end_x, 1] = color[1]  # G
            palette_img[:, start_x:end_x, 2] = color[2]  # B
            
            print(f"  位置 {start_x:3d}-{end_x:3d}: RGB{color}")
        
        # 表示
        plt.figure(figsize=(12, 8))
        
        plt.subplot(2, 1, 1)
        plt.imshow(sample_new)
        plt.title("元画像", fontsize=14)
        plt.axis('off')
        
        plt.subplot(2, 1, 2)
        plt.imshow(palette_img)
        plt.title(f"{image_title} - 5色カラーパレット", fontsize=14)
        plt.axis('off')
        
        # 境界線を追加
        for i in range(1, n_colors):
            plt.axvline(x=i * color_width, color='white', linewidth=3)
        
        # 各色の名前を表示
        for i, (color, count) in enumerate(colors_list):
            x_pos = (i + 0.5) * color_width
            plt.text(x_pos, palette_height//2, f'RGB\n{color}', 
                    ha='center', va='center', fontweight='bold', fontsize=10,
                    bbox=dict(boxstyle="round,pad=0.3", facecolor='white', alpha=0.8))
        
        plt.tight_layout()
        plt.show()
        
        return palette_img
    
    # パレット表示
    palette_correct = display_correct_palette(top_5_colors, "強力量子化版")
    画像

    ✨ ここまでのまとめ

    お疲れ様でした!ここまでで以下のことができるようになりました:

    ✅ 画像の読み込みと基本情報取得
    ✅ RGB値の理解と色の可視化
    ✅ 最頻値色の抽出
    ✅ 色分布の統計的分析
    ✅ シンプルなカラーパレット生成

    これだけでも、写真から主要な色を抽出する基本的な画像解析ツールが完成しました!

    現在のアプリには以下の課題があります

    ❌ 単純な頻度カウントなので、似たような色が複数選ばれる
    ❌ ノイズや微細な色差に影響されやすい
    ❌ HEXコード出力がない(Web制作で使いづらい)
    ❌ 占有率の可視化ができない
    ❌ 実用的なファイル出力機能がない

    これらを解決するには、機械学習の「K-meansクラスタリング」という手法を使います。

    K-meansクラスタリングは、教師なし学習の代表的なクラスタリング手法です。データをあらかじめ指定した数 k 個のグループ(クラスタ)に分けるアルゴリズムです。


    基本的な考え方

    • データの「近さ(距離)」に基づいて、似ているデータ同士を1つのクラスタにまとめます。

    • 各クラスタの「中心(セントロイド)」を計算し、それを基準にデータを分類していきます。


    アルゴリズムの流れ

    1. クラスタ数 k を決める
      例:3つのグループに分けたいなら k = 3。

    2. 初期のクラスタ中心をランダムに選ぶ
      データ空間からランダムに kk 個の点を選び、仮の中心とする。

    3. データをクラスタに割り当てる
      各データ点を「一番近い中心」に属するクラスタへ分類する。

    4. 中心を更新する
      各クラスタに属する点の平均値を取り、それを新しい中心にする。

    5. 収束判定
      データの割り当てが変わらなくなるか、中心がほとんど動かなくなったら終了。


    イメージ例

    例えば「身長」と「体重」のデータをクラスタリングした場合:

    • k=2 に設定すると、「小柄で軽いグループ」と「大柄で重いグループ」に分けられる。

    • k = 3 にすると「小柄」「中くらい」「大柄」の3つに分かれることもある。


    メリット

    • シンプルで実装しやすい。

    • 計算が比較的速い。

    デメリット

    • あらかじめ kk を決めなければならない。

    • クラスタの形が「球状(丸い形)」に近い場合にうまくいくが、複雑な形には弱い。

    • 初期値の設定によって結果が変わることがある。


    👉 まとめると、K-meansクラスタリングは
    「データを似ているグループに分けるシンプルな方法」であり、マーケティングの顧客分類、画像圧縮、異常検知など幅広く使われています。

    有料部分では、以下の内容をお届けします。

    🧠 高度な技術を学べます

    • K-meansクラスタリングで高精度な色抽出

    • scikit-learnを使った機械学習入門

    • 色空間変換(RGB ↔ HSV)の理解

    🎨 実用的な機能が完成します

    • HEXコード自動生成(#FF5733形式)

    • 占有率円グラフで視覚的分析

    • PNG形式でパレット保存

    • 複数画像の一括処理

    📚 応用・発展内容も充実

    • 色の類似度判定機能

    • 季節感推定アルゴリズム

    • 動画ファイル対応

    • リアルタイム処理への拡張


    画像解析はAI・データサイエンス分野への第一歩でもあります。この技術をマスターすれば、Instagram分析、商品画像の自動分類、Webデザイン支援ツールなど、様々な応用が可能になります。

    ぜひチャレンジしてみてください! 🎉

    【有料部分】プロ級カラーパレット生成アプリの完成版

    🎯 機械学習で高精度な色抽出を実現しよう

    無料部分では単純な頻度カウントで色抽出を行いましたが、これだけでは実用的ではありません。プロ級のツールにするため、K-meansクラスタリングという機械学習手法を導入します。

    K-meansクラスタリングとは?

    K-meansは「似た特徴を持つデータを自動的にグループ分けする」アルゴリズムです。色抽出では、似た色をまとめて代表色を決めることで、美しく実用的なカラーパレットを生成できます。

    # 機械学習ライブラリの追加インストール
    !pip install scikit-learn
    
    from sklearn.cluster import KMeans
    from sklearn.utils import shuffle
    import webcolors
    

    🧠 Step5:K-meansで高精度色抽出

    def extract_colors_kmeans(image, n_colors=5, max_pixels=10000):
        """
        K-meansクラスタリングを使った高精度な色抽出
        
        Args:
            image: PIL画像オブジェクト
            n_colors: 抽出する色数
            max_pixels: 処理速度向上のため、使用する最大ピクセル数
        
        Returns:
            colors: 抽出された色のリスト
            percentages: 各色の占有率
        """
        print(f"🧠 K-meansクラスタリングで{n_colors}色を抽出中...")
        
        # 画像をNumPy配列に変換
        rgb_array = np.array(image)
        pixels = rgb_array.reshape(-1, 3)
        
        # 大きな画像の場合、処理速度向上のためピクセルをサンプリング
        if len(pixels) > max_pixels:
            pixels = shuffle(pixels, random_state=42, n_samples=max_pixels)
            print(f"⚡ 高速化のため{len(pixels):,}ピクセルをサンプリング")
        
        # K-meansクラスタリング実行
        kmeans = KMeans(n_clusters=n_colors, random_state=42, n_init=10)
        kmeans.fit(pixels)
        
        # クラスター中心(代表色)を取得
        colors = kmeans.cluster_centers_.astype(int)
        
        # 各ピクセルがどのクラスターに属するか予測
        labels = kmeans.labels_
        
        # 各色の占有率を計算
        label_counts = np.bincount(labels)
        percentages = (label_counts / len(labels)) * 100
        
        print(f"✅ 抽出完了!各色の占有率:")
        for i, (color, pct) in enumerate(zip(colors, percentages)):
            print(f"  色{i+1}: RGB{tuple(color)} - {pct:.1f}%")
        
        return colors, percentages
    
    # サンプル画像で実行
    extracted_colors, percentages = extract_colors_kmeans(sample_pil, n_colors=5)
    
    画像
    🧠 K-meansクラスタリングで5色を抽出中...
    ⚡ 高速化のため10,000ピクセルをサンプリング
    ✅ 抽出完了!各色の占有率:
    色1: RGB(np.int64(138), np.int64(68), np.int64(18)) - 20.0%
    色2: RGB(np.int64(134), np.int64(205), np.int64(234)) - 24.9%
    色3: RGB(np.int64(33), np.int64(138), np.int64(33)) - 25.3%
    色4: RGB(np.int64(252), np.int64(214), np.int64(2)) - 12.4%
    色5: RGB(np.int64(252), np.int64(191), np.int64(202)) - 17.4%

    🎨 Step6:美しいカラーパレット表示とHEXコード生成

    def rgb_to_hex(rgb_tuple):
        """
        RGB値をHEXコード(#FF5733形式)に変換
        """
        return "#{:02x}{:02x}{:02x}".format(int(rgb_tuple[0]), int(rgb_tuple[1]), int(rgb_tuple[2]))
    
    def get_color_name(rgb_tuple):
        """
        RGB値から近似色名を取得(可能な場合)
        """
        try:
            # webcolorsライブラリを使って色名を取得
            color_name = webcolors.rgb_to_name(rgb_tuple)
            return color_name
        except ValueError:
            # 完全一致しない場合は最近傍色を探す
            min_colors = {}
            for key, name in webcolors.CSS3_HEX_TO_NAMES.items():
                r_c, g_c, b_c = webcolors.hex_to_rgb(key)
                rd = (r_c - rgb_tuple[0]) ** 2
                gd = (g_c - rgb_tuple[1]) ** 2
                bd = (b_c - rgb_tuple[2]) ** 2
                min_colors[(rd + gd + bd)] = name
            return min_colors[min(min_colors.keys())]
    
    def create_professional_palette(colors, percentages, palette_width=800, palette_height=150):
        """
        プロフェッショナルなカラーパレット画像を生成
        """
        n_colors = len(colors)
        
        # パレット画像作成
        palette_img = np.zeros((palette_height, palette_width, 3), dtype=np.uint8)
        
        # 占有率に応じて幅を調整
        current_x = 0
        color_info = []
        
        for i, (color, pct) in enumerate(zip(colors, percentages)):
            # 占有率に応じた幅を計算
            width = int((pct / 100) * palette_width)
            if i == n_colors - 1:  # 最後の色は残り全体を埋める
                width = palette_width - current_x
            
            # 色を配置
            palette_img[:, current_x:current_x + width] = color
            
            # 色情報を記録
            hex_code = rgb_to_hex(color)
            try:
                color_name = get_color_name(tuple(color))
            except:
                color_name = "Unknown"
            
            color_info.append({
                'rgb': tuple(color),
                'hex': hex_code,
                'name': color_name,
                'percentage': pct,
                'position': current_x + width // 2
            })
            
            current_x += width
        
        return palette_img, color_info
    
    # プロフェッショナルパレット生成
    palette_img, color_info = create_professional_palette(extracted_colors, percentages)
    
    # 表示
    plt.figure(figsize=(16, 10))
    
    # 元画像
    plt.subplot(3, 1, 1)
    plt.imshow(sample_pil)
    plt.title("元画像", fontsize=16)
    plt.axis('off')
    
    # カラーパレット(占有率反映)
    plt.subplot(3, 1, 2)
    plt.imshow(palette_img)
    plt.title("カラーパレット(占有率反映)", fontsize=16)
    plt.axis('off')
    
    # HEXコードとパーセンテージを表示
    for info in color_info:
        plt.text(info['position'], palette_img.shape[0] // 2, 
                 f"{info['hex']}\n{info['percentage']:.1f}%", 
                 ha='center', va='center', 
                 fontsize=10, fontweight='bold',
                 bbox=dict(boxstyle="round,pad=0.3", facecolor='white', alpha=0.8))
    
    # 均等幅パレット(デザイン用)
    plt.subplot(3, 1, 3)
    equal_palette = np.zeros((100, 800, 3), dtype=np.uint8)
    color_width = 800 // len(extracted_colors)
    for i, color in enumerate(extracted_colors):
        start_x = i * color_width
        end_x = start_x + color_width
        equal_palette[:, start_x:end_x] = color
    
    plt.imshow(equal_palette)
    plt.title("均等幅カラーパレット(デザイン用)", fontsize=16)
    plt.axis('off')
    
    # HEXコードを表示
    for i, info in enumerate(color_info):
        x_pos = (i + 0.5) * color_width
        plt.text(x_pos, 50, info['hex'], 
                 ha='center', va='center', 
                 fontsize=12, fontweight='bold',
                 bbox=dict(boxstyle="round,pad=0.3", facecolor='white', alpha=0.9))
    
    plt.tight_layout()
    plt.show()
    
    # 色情報を表形式で表示
    print("🎨 抽出された色の詳細情報:")
    print("-" * 80)
    print(f"{'順位':<4} {'RGB値':<15} {'HEXコード':<10} {'色名':<15} {'占有率':<8}")
    print("-" * 80)
    for i, info in enumerate(color_info):
        print(f"{i+1:<4} {str(info['rgb']):<15} {info['hex']:<10} {info['name']:<15} {info['percentage']:.1f}%")
    
    画像

    🎨 抽出された色の詳細情報:
    --------------------------------------------------------------------------------
    順位 RGB値 HEXコード 色名 占有率
    --------------------------------------------------------------------------------
    1 (np.int64(138), np.int64(68), np.int64(18)) #8a4412 Unknown 20.0%
    2 (np.int64(134), np.int64(205), np.int64(234)) #86cdea Unknown 24.9%
    3 (np.int64(33), np.int64(138), np.int64(33)) #218a21 Unknown 25.3%
    4 (np.int64(252), np.int64(214), np.int64(2)) #fcd602 Unknown 12.4%
    5 (np.int64(252), np.int64(191), np.int64(202)) #fcbfca Unknown 17.4%


    📊 Step7:占有率を円グラフで可視化

    def create_color_pie_chart(colors, percentages, color_info):
        """
        色の占有率を美しい円グラフで表示
        """
        # 色を正規化(0-1の範囲に変換)
        colors_normalized = [tuple(c/255 for c in color) for color in colors]
        
        plt.figure(figsize=(12, 8))
        
        # 円グラフ作成
        plt.subplot(1, 2, 1)
        wedges, texts, autotexts = plt.pie(
            percentages, 
            colors=colors_normalized,
            labels=[f"色{i+1}" for i in range(len(colors))],
            autopct='%1.1f%%',
            startangle=90,
            textprops={'fontsize': 12}
        )
        
        # 円グラフを美しく装飾
        for autotext in autotexts:
            autotext.set_color('white')
            autotext.set_fontweight('bold')
            autotext.set_fontsize(10)
        
        plt.title("色の占有率分析", fontsize=16, fontweight='bold')
        
        # 凡例作成
        plt.subplot(1, 2, 2)
        plt.axis('off')
        
        legend_y = 0.9
        plt.text(0.1, 0.95, "📊 カラー分析レポート", fontsize=16, fontweight='bold')
        
        for i, info in enumerate(color_info):
            # 色見本
            color_rect = plt.Rectangle((0.1, legend_y - 0.05), 0.08, 0.08, 
                                     facecolor=tuple(c/255 for c in info['rgb']))
            plt.gca().add_patch(color_rect)
            
            # 色情報テキスト
            plt.text(0.25, legend_y, f"色{i+1}: {info['hex']}", fontsize=12, fontweight='bold')
            plt.text(0.25, legend_y - 0.03, f"RGB{info['rgb']}", fontsize=10)
            plt.text(0.25, legend_y - 0.06, f"{info['name']} ({info['percentage']:.1f}%)", fontsize=10)
            
            legend_y -= 0.15
        
        plt.xlim(0, 1)
        plt.ylim(0, 1)
        
        plt.tight_layout()
        plt.show()
    
    # 円グラフ作成
    create_color_pie_chart(extracted_colors, percentages, color_info)
    
    画像

    🏗️ Step8:完成版クラス設計

    実用的なアプリにするため、すべての機能をクラスにまとめましょう。

    class ColorPaletteExtractor:
        """
        プロフェッショナルなカラーパレット抽出クラス
        """
        
        def __init__(self, n_colors=5, max_pixels=10000):
            self.n_colors = n_colors
            self.max_pixels = max_pixels
            self.colors = None
            self.percentages = None
            self.color_info = None
        
        def extract_palette(self, image):
            """
            画像からカラーパレットを抽出
            """
            print(f"🎨 カラーパレット抽出開始...")
            
            # K-meansクラスタリング
            rgb_array = np.array(image)
            pixels = rgb_array.reshape(-1, 3)
            
            if len(pixels) > self.max_pixels:
                pixels = shuffle(pixels, random_state=42, n_samples=self.max_pixels)
            
            kmeans = KMeans(n_clusters=self.n_colors, random_state=42, n_init=10)
            kmeans.fit(pixels)
            
            self.colors = kmeans.cluster_centers_.astype(int)
            labels = kmeans.labels_
            label_counts = np.bincount(labels)
            self.percentages = (label_counts / len(labels)) * 100
            
            # 占有率順にソート
            sorted_indices = np.argsort(self.percentages)[::-1]
            self.colors = self.colors[sorted_indices]
            self.percentages = self.percentages[sorted_indices]
            
            # 色情報を生成
            self._generate_color_info()
            
            print(f"✅ {self.n_colors}色のパレット抽出完了!")
            return self
        
        def _generate_color_info(self):
            """
            色情報(HEXコード、色名など)を生成
            """
            self.color_info = []
            for color, pct in zip(self.colors, self.percentages):
                rgb_tuple = tuple(color)
                hex_code = "#{:02x}{:02x}{:02x}".format(int(color[0]), int(color[1]), int(color[2]))
                
                try:
                    color_name = webcolors.rgb_to_name(rgb_tuple)
                except ValueError:
                    color_name = self._get_closest_color_name(rgb_tuple)
                
                self.color_info.append({
                    'rgb': rgb_tuple,
                    'hex': hex_code,
                    'name': color_name,
                    'percentage': pct
                })
        
        def _get_closest_color_name(self, rgb_tuple):
            """
            最も近い色名を取得
            """
            min_colors = {}
            for key, name in webcolors.CSS3_HEX_TO_NAMES.items():
                r_c, g_c, b_c = webcolors.hex_to_rgb(key)
                rd = (r_c - rgb_tuple[0]) ** 2
                gd = (g_c - rgb_tuple[1]) ** 2
                bd = (b_c - rgb_tuple[2]) ** 2
                min_colors[(rd + gd + bd)] = name
            return min_colors[min(min_colors.keys())]
        
        def visualize_palette(self, show_pie_chart=True):
            """
            カラーパレットを可視化
            """
            if self.colors is None:
                print("❌ まずextract_palette()を実行してください")
                return
            
            fig_height = 12 if show_pie_chart else 8
            plt.figure(figsize=(16, fig_height))
            
            subplot_count = 3 if show_pie_chart else 2
            
            # 占有率反映パレット
            plt.subplot(subplot_count, 1, 1)
            palette_img = self._create_proportional_palette()
            plt.imshow(palette_img)
            plt.title("カラーパレット(占有率反映)", fontsize=16)
            plt.axis('off')
            
            # 均等幅パレット
            plt.subplot(subplot_count, 1, 2)
            equal_palette = self._create_equal_palette()
            plt.imshow(equal_palette)
            plt.title("均等幅カラーパレット(デザイン用)", fontsize=16)
            plt.axis('off')
            
            # HEXコード表示
            color_width = 800 // len(self.colors)
            for i, info in enumerate(self.color_info):
                x_pos = (i + 0.5) * color_width
                plt.text(x_pos, 50, info['hex'], 
                         ha='center', va='center', 
                         fontsize=12, fontweight='bold',
                         bbox=dict(boxstyle="round,pad=0.3", facecolor='white', alpha=0.9))
            
            # 円グラフ
            if show_pie_chart:
                plt.subplot(subplot_count, 1, 3)
                colors_normalized = [tuple(c/255 for c in color) for color in self.colors]
                plt.pie(self.percentages, colors=colors_normalized, 
                        labels=[f"色{i+1}" for i in range(len(self.colors))],
                        autopct='%1.1f%%', startangle=90)
                plt.title("色の占有率", fontsize=16)
            
            plt.tight_layout()
            plt.show()
        
        def _create_proportional_palette(self, width=800, height=100):
            """
            占有率に応じたパレット画像を作成
            """
            palette_img = np.zeros((height, width, 3), dtype=np.uint8)
            current_x = 0
            
            for i, (color, pct) in enumerate(zip(self.colors, self.percentages)):
                color_width = int((pct / 100) * width)
                if i == len(self.colors) - 1:
                    color_width = width - current_x
                
                palette_img[:, current_x:current_x + color_width] = color
                current_x += color_width
            
            return palette_img
        
        def _create_equal_palette(self, width=800, height=100):
            """
            均等幅のパレット画像を作成
            """
            palette_img = np.zeros((height, width, 3), dtype=np.uint8)
            color_width = width // len(self.colors)
            
            for i, color in enumerate(self.colors):
                start_x = i * color_width
                end_x = start_x + color_width
                palette_img[:, start_x:end_x] = color
            
            return palette_img
        
        def save_palette(self, filename="color_palette.png"):
            """
            カラーパレットをPNG形式で保存
            """
            if self.colors is None:
                print("❌ まずextract_palette()を実行してください")
                return
            
            equal_palette = self._create_equal_palette(width=1000, height=200)
            palette_pil = Image.fromarray(equal_palette)
            palette_pil.save(filename)
            print(f"💾 カラーパレットを{filename}として保存しました")
            
            return filename
        
        def get_color_codes(self):
            """
            HEXコードのリストを取得
            """
            if self.color_info is None:
                return None
            return [info['hex'] for info in self.color_info]
        
        def get_css_variables(self):
            """
            CSS変数形式で出力
            """
            if self.color_info is None:
                return None
            
            css_vars = [
                f"--color-{i+1}: {info['hex']};" 
                for i, info in enumerate(self.color_info)
            ]
            return "\n".join(css_vars)
        
        def print_report(self):
            """
            詳細レポートを出力
            """
            if self.color_info is None:
                print("❌ まずextract_palette()を実行してください")
                return
            
            print("🎨 " + "="*60)
            print("       カラーパレット分析レポート")
            print("="*64)
            print(f"抽出色数: {len(self.colors)}色")
            print(f"解析手法: K-meansクラスタリング")
            print("-" * 64)
            print(f"{'順位':<4} {'RGB値':<15} {'HEXコード':<10} {'色名':<15} {'占有率':<8}")
            print("-" * 64)
            
            for i, info in enumerate(self.color_info):
                print(f"{i+1:<4} {str(info['rgb']):<15} {info['hex']:<10} {info['name']:<15} {info['percentage']:.1f}%")
            
            print("-" * 64)
            print("💡 使用例:")
            print("  • Webデザイン: CSS変数として活用")
            print("  • グラフィック: Adobe製品のスウォッチにインポート")  
            print("  • 印刷物: CMYK変換してDTPに活用")
            print("="*64)
    
    # 使用例
    extractor = ColorPaletteExtractor(n_colors=5)
    extractor.extract_palette(sample_pil)
    extractor.visualize_palette()
    extractor.print_report()
    
    画像

    🎨 =======================================================カラーパレット分析レポート =========================================================抽出色数: 5色 解析手法: K-meansクラスタリング
    ----------------------------------------------------------------
    順位 RGB値 HEXコード 色名 占有率
    ----------------------------------------------------------------
    1 (134, 206, 235) #86ceeb skyblue 25.4%
    2 (33, 139, 33) #218b21 forestgreen 24.4%
    3 (139, 69, 19) #8b4513 saddlebrown 17.3%
    4 (254, 215, 0) #fed700 gold 16.8%
    5 (254, 192, 202) #fec0ca pink 16.1%
    ----------------------------------------------------------------
    💡 使用例:
    • Webデザイン: CSS変数として活用
    • グラフィック: Adobe製品のスウォッチにインポート
    • 印刷物: CMYK変換してDTPに活用 =========================================================


    💾 Step9:ファイル出力とバッチ処理

    def process_multiple_images():
        """
        複数画像の一括処理(修正版)
        """
        # サンプル画像を複数作成(型を明示的に指定)
        sample_images = []
        
        print("🖼️ サンプル画像を生成中...")
        
        # 異なるタイプの画像を生成
        for i in range(3):
            print(f"  画像{i+1}を作成中...")
            
            if i == 0:  # 暖色系画像
                print("    -> 暖色系(夕焼け風)")
                img = np.random.randint(150, 255, (150, 150, 3), dtype=np.uint8)
                img[:, :, 2] = np.random.randint(50, 120, (150, 150), dtype=np.uint8)  # 青を抑制
                
            elif i == 1:  # 寒色系画像
                print("    -> 寒色系(海・空風)")
                img = np.random.randint(50, 200, (150, 150, 3), dtype=np.uint8)
                img[:, :, 0] = np.random.randint(30, 100, (150, 150), dtype=np.uint8)  # 赤を抑制
                img[:, :, 2] = np.random.randint(150, 255, (150, 150), dtype=np.uint8)  # 青を強調
                
            else:  # モノクロ系画像
                print("    -> モノクロ系(都市風)")
                gray_values = np.random.randint(80, 180, (150, 150), dtype=np.uint8)
                img = np.stack([gray_values, gray_values, gray_values], axis=2)
            
            # 配列の型とshapeを確認
            print(f"      配列shape: {img.shape}, dtype: {img.dtype}")
            
            # PIL画像に変換(型チェック付き)
            try:
                pil_img = Image.fromarray(img)
                sample_images.append(pil_img)
                print(f"      ✅ PIL変換成功")
            except Exception as e:
                print(f"      ❌ PIL変換エラー: {e}")
                # エラー時は安全な画像を作成
                safe_img = np.full((150, 150, 3), [128, 128, 128], dtype=np.uint8)
                sample_images.append(Image.fromarray(safe_img))
        
        print(f"✅ {len(sample_images)}枚のサンプル画像生成完了")
        
        # 一括処理実行
        results = []
        for i, img in enumerate(sample_images):
            print(f"\n📸 画像 {i+1} を処理中...")
            try:
                extractor = ColorPaletteExtractor(n_colors=5)
                extractor.extract_palette(img)
                results.append(extractor)
                
                # ファイル保存(エラーハンドリング付き)
                filename = f"palette_{i+1}.png"
                try:
                    extractor.save_palette(filename)
                    print(f"  💾 {filename} 保存成功")
                except Exception as e:
                    print(f"  ❌ ファイル保存エラー: {e}")
                    
            except Exception as e:
                print(f"  ❌ 色抽出エラー: {e}")
                results.append(None)
        
        # 結果をまとめて表示(成功したもののみ)
        valid_results = [(img, res) for img, res in zip(sample_images, results) if res is not None]
        
        if len(valid_results) > 0:
            print(f"\n🎨 {len(valid_results)}枚の結果を表示中...")
            plt.figure(figsize=(18, 6 * len(valid_results)))
            
            for i, (img, extractor) in enumerate(valid_results):
                row_base = i * 3
                
                # 元画像
                plt.subplot(len(valid_results), 3, row_base + 1)
                plt.imshow(img)
                plt.title(f"画像{i+1}", fontsize=12)
                plt.axis('off')
                
                # パレット
                plt.subplot(len(valid_results), 3, row_base + 2)
                try:
                    palette = extractor._create_equal_palette(width=400, height=100)
                    plt.imshow(palette)
                    plt.title(f"パレット{i+1}", fontsize=12)
                    plt.axis('off')
                except Exception as e:
                    plt.text(0.5, 0.5, f"パレット表示エラー:\n{str(e)}", 
                            ha='center', va='center', transform=plt.gca().transAxes)
                    plt.title(f"パレット{i+1} (エラー)", fontsize=12)
                
                # 円グラフ
                plt.subplot(len(valid_results), 3, row_base + 3)
                try:
                    colors_norm = [tuple(c/255 for c in color) for color in extractor.colors]
                    plt.pie(extractor.percentages, colors=colors_norm, 
                            startangle=90, textprops={'fontsize': 8})
                    plt.title(f"占有率{i+1}", fontsize=12)
                except Exception as e:
                    plt.text(0.5, 0.5, f"円グラフエラー:\n{str(e)}", 
                            ha='center', va='center', transform=plt.gca().transAxes)
                    plt.title(f"占有率{i+1} (エラー)", fontsize=12)
            
            plt.tight_layout()
            plt.show()
        
        return results
    
    # ColorPaletteExtractorにsave_paletteメソッドを追加
    def save_palette_method(self, filename="color_palette.png"):
        """
        カラーパレットをPNG形式で保存(修正版)
        """
        if self.colors is None:
            print("❌ まずextract_palette()を実行してください")
            return None
        
        try:
            # パレット画像作成(型を明示的に指定)
            equal_palette = self._create_equal_palette(width=1000, height=200)
            
            # 型とshapeを確認
            print(f"🔍 保存前チェック - shape: {equal_palette.shape}, dtype: {equal_palette.dtype}")
            
            # uint8型であることを確認
            if equal_palette.dtype != np.uint8:
                equal_palette = equal_palette.astype(np.uint8)
                print("🔧 uint8型に変換しました")
            
            # PIL画像に変換
            palette_pil = Image.fromarray(equal_palette)
            
            # 保存
            palette_pil.save(filename)
            print(f"💾 カラーパレットを{filename}として保存しました")
            
            return filename
            
        except Exception as e:
            print(f"❌ 保存中にエラーが発生: {e}")
            return None
    
    # メソッドを追加
    ColorPaletteExtractor.save_palette = save_palette_method
    
    # バッチ処理実行
    print("🚀 バッチ処理を開始...")
    batch_results = process_multiple_images()
    
    # CSS出力例(成功したもののみ)
    print("\n🎨 CSS変数出力例:")
    for i, result in enumerate(batch_results):
        if result is not None:
            print(f"\n/* 画像{i+1}のカラーパレット */")
            css_output = result.get_css_variables()
            if css_output:
                print(css_output)
            else:
                print("/* CSS変数生成エラー */")
    画像
    画像
    画像
    画像

    🎨 CSS変数出力例:
    画像1のカラーパレット
    --color-1: #e3e554 ;
    --color-2: #e7af54 ;
    --color-3: #aee654 ;
    --color-4: #b2b341 ;
    --color-5: #b2b167 ;
    画像2のカラーパレット
    --color-1: #4057ad ;
    --color-2: #40a3ad ;
    --color-3: #3f7cdf ;
    --color-4: #4049e2 ;
    --color-5: #40afe2 ;
    画像3のカラーパレット
    --color-1: #828282 ;
    --color-2: #969696 ;
    --color-3: #595959 ;
    --color-4: #aaaaaa ;
    --color-5: #6d6d6d ;


    🔍 Step10:色の類似度判定と高度な分析

    def calculate_color_similarity(color1, color2):
        """
        2つの色の類似度を計算(ユークリッド距離使用)
        """
        r1, g1, b1 = color1
        r2, g2, b2 = color2
        return np.sqrt((r1-r2)**2 + (g1-g2)**2 + (b1-b2)**2)
    
    def analyze_color_harmony(colors):
        """
        色の調和性を分析
        """
        harmonies = {
            'complementary': [],
            'analogous': [],
            'triadic': [],
            'split_complementary': []
        }
        
        # RGB to HSV変換で色相を分析
        hsv_colors = []
        for color in colors:
            r, g, b = [x/255.0 for x in color]
            h, s, v = colorsys.rgb_to_hsv(r, g, b)
            hsv_colors.append((h*360, s, v))  # 色相を度数に変換
        
        print("🎨 色彩調和分析:")
        print("-" * 40)
        
        for i, (rgb, hsv) in enumerate(zip(colors, hsv_colors)):
            h, s, v = hsv
            print(f"色{i+1}: H={h:.0f}° S={s:.2f} V={v:.2f}")
            
            # 色相に基づく分類
            if h < 30 or h >= 330:
                color_family = "赤系"
            elif h < 90:
                color_family = "黄系"
            elif h < 150:
                color_family = "緑系"
            elif h < 210:
                color_family = "青系"
            elif h < 270:
                color_family = "紫系"
            else:
                color_family = "マゼンタ系"
            
            print(f"       色系統: {color_family}")
        
        # 色相の分布分析
        hues = [hsv[0] for hsv in hsv_colors]
        hue_range = max(hues) - min(hues)
        
        print(f"\n📊 色相分析:")
        print(f"色相の範囲: {hue_range:.1f}°")
        
        if hue_range < 30:
            harmony_type = "類似色調和(Analogous)"
        elif hue_range < 60:
            harmony_type = "近似類似色調和"
        elif 150 < hue_range < 210:
            harmony_type = "補色調和(Complementary)"
        else:
            harmony_type = "多色調和(Polychromatic)"
        
        print(f"調和タイプ: {harmony_type}")
        
        return harmony_type
    
    def suggest_color_applications(extractor):
        """
        色の特徴に基づいて用途を提案
        """
        if extractor.color_info is None:
            return
        
        # 色の明度・彩度を分析
        total_brightness = 0
        total_saturation = 0
        
        for info in extractor.color_info:
            r, g, b = info['rgb']
            brightness = (r + g + b) / 3
            saturation = (max(r, g, b) - min(r, g, b)) / 255
            total_brightness += brightness * (info['percentage'] / 100)
            total_saturation += saturation * (info['percentage'] / 100)
        
        print("\n💡 カラーパレット活用提案:")
        print("=" * 50)
        
        # 明度による提案
        if total_brightness > 200:
            print("🌟 明るい色調 → 春・夏向けデザイン、ポップなWebサイト")
        elif total_brightness > 150:
            print("☀️ 中明度 → オールシーズン対応、企業サイト")
        else:
            print("🌙 暗い色調 → 秋・冬向けデザイン、高級感のある表現")
        
        # 彩度による提案
        if total_saturation > 0.7:
            print("🎨 高彩度 → エネルギッシュな表現、子供向けコンテンツ")
        elif total_saturation > 0.4:
            print("🎭 中彩度 → バランスの良い表現、一般的な用途")
        else:
            print("🏛️ 低彩度 → 落ち着いた表現、プロフェッショナル用途")
        
        # 色数による提案
        if len(extractor.colors) <= 3:
            print("🎯 ミニマル → ロゴデザイン、シンプルなUI")
        else:
            print("🌈 リッチ → 複雑なデザイン、アート作品")
        
        print("=" * 50)
    
    import colorsys
    
    # 高度な分析実行例
    print("🔬 高度な色彩分析を実行中...")
    extractor = ColorPaletteExtractor(n_colors=5)
    extractor.extract_palette(sample_pil)
    
    harmony_type = analyze_color_harmony(extractor.colors)
    suggest_color_applications(extractor)
    
    画像

    🔬 高度な色彩分析を実行中...
    🎨 カラーパレット抽出開始...
    ✅ 5色のパレット抽出完了!
    🎨 色彩調和分析:
    ----------------------------------------
    色1: H=120° S=0.76 V=0.54 色系統: 緑系
    色2: H=197° S=0.43 V=0.92 色系統: 青系
    色3: H=25° S=0.87 V=0.54 色系統: 赤系
    色4: H=349° S=0.24 V=0.99 色系統: 赤系
    色5: H=51° S=0.99 V=0.99 色系統: 黄系

    📊 色相分析:
    色相の範囲: 324.2°
    調和タイプ: 多色調和(Polychromatic)

    💡 カラーパレット活用提案: ==================================================
    🌙 暗い色調 → 秋・冬向けデザイン、高級感のある表現
    🎭 中彩度 → バランスの良い表現、一般的な用途
    🌈 リッチ → 複雑なデザイン、アート作品 ==================================================


    🌍 Step11:実際の写真での応用例

    # 実際の画像での応用を想定したサンプル
    def create_realistic_sample_images():
        """
        より現実的なサンプル画像を生成
        """
        samples = {}
        
        # 1. 自然風景風(空と草原)
        landscape = np.zeros((200, 300, 3), dtype=np.uint8)
        # 空部分(上半分)
        landscape[:100, :, :] = [135, 206, 235]  # スカイブルー
        # 雲
        for i in range(20):
            x = np.random.randint(0, 280)
            y = np.random.randint(10, 90)
            cv2.circle(landscape, (x, y), np.random.randint(15, 25), (255, 255, 255), -1)
        # 草原部分(下半分)
        landscape[100:, :, :] = [34, 139, 34]  # 森林緑
        # 花を追加
        for i in range(30):
            x = np.random.randint(10, 290)
            y = np.random.randint(120, 190)
            cv2.circle(landscape, (x, y), 3, (255, 192, 203), -1)  # ピンクの花
        
        samples['landscape'] = Image.fromarray(landscape)
        
        # 2. カフェ風(暖色系)
        cafe = np.full((200, 300, 3), [139, 69, 19], dtype=np.uint8)  # ブラウンベース
        # テーブル
        cafe[150:, :, :] = [160, 82, 45]
        # コーヒーカップ
        cv2.circle(cafe, (150, 170), 20, (255, 255, 255), -1)
        cv2.circle(cafe, (150, 170), 15, (101, 67, 33), -1)
        # 植物
        cv2.circle(cafe, (250, 50), 30, (34, 139, 34), -1)
        
        samples['cafe'] = Image.fromarray(cafe)
        
        # 3. 夕焼け風
        sunset = np.zeros((200, 300, 3), dtype=np.uint8)
        for y in range(200):
            ratio = y / 200
            if y < 100:  # 空部分
                r = int(255 * (1 - ratio * 0.3))
                g = int(165 * (1 - ratio * 0.8))
                b = int(0 * (1 - ratio))
            else:  # 地面部分
                r = int(139 * ratio)
                g = int(69 * ratio)
                b = int(19 * ratio)
            sunset[y, :, :] = [r, g, b]
        
        samples['sunset'] = Image.fromarray(sunset)
        
        return samples
    
    # OpenCVがない場合の代替実装
    def create_simple_realistic_samples():
        """
        OpenCVを使わないバージョン
        """
        samples = {}
        
        # 自然風景風
        landscape = np.zeros((200, 300, 3), dtype=np.uint8)
        landscape[:100, :, :] = [135, 206, 235]  # 空
        landscape[100:, :, :] = [34, 139, 34]    # 草
        samples['landscape'] = Image.fromarray(landscape)
        
        # カフェ風
        cafe = np.full((200, 300, 3), [139, 69, 19], dtype=np.uint8)
        samples['cafe'] = Image.fromarray(cafe)
        
        # 夕焼け風
        sunset = np.zeros((200, 300, 3), dtype=np.uint8)
        for y in range(200):
            ratio = y / 200
            r = int(255 - 100 * ratio)
            g = int(165 - 100 * ratio)
            b = int(50 * ratio)
            sunset[y, :, :] = [max(0, r), max(0, g), max(0, b)]
        samples['sunset'] = Image.fromarray(sunset)
        
        return samples
    
    # サンプル画像で分析実行
    realistic_samples = create_simple_realistic_samples()
    
    plt.figure(figsize=(18, 15))
    
    for i, (name, image) in enumerate(realistic_samples.items()):
        # 各画像の分析
        extractor = ColorPaletteExtractor(n_colors=5)
        extractor.extract_palette(image)
        
        # 表示
        row = i
        
        # 元画像
        plt.subplot(3, 3, row*3 + 1)
        plt.imshow(image)
        plt.title(f"{name.capitalize()} 画像", fontsize=12)
        plt.axis('off')
        
        # カラーパレット
        plt.subplot(3, 3, row*3 + 2)
        palette = extractor._create_equal_palette()
        plt.imshow(palette)
        plt.title(f"{name.capitalize()} パレット", fontsize=12)
        plt.axis('off')
        
        # HEXコード表示
        hex_codes = extractor.get_color_codes()
        y_pos = 0.8
        for j, hex_code in enumerate(hex_codes):
            plt.text(0.1, y_pos - j*0.15, hex_code, fontsize=10, 
                    bbox=dict(boxstyle="round,pad=0.3", facecolor='lightgray'))
        
        # 円グラフ
        plt.subplot(3, 3, row*3 + 3)
        colors_norm = [tuple(c/255 for c in color) for color in extractor.colors]
        plt.pie(extractor.percentages, colors=colors_norm, startangle=90, 
                textprops={'fontsize': 8})
        plt.title(f"{name.capitalize()} 分布", fontsize=12)
        
        # コンソール出力
        print(f"\n🎨 {name.upper()}の分析結果:")
        extractor.print_report()
        analyze_color_harmony(extractor.colors)
    
    plt.tight_layout()
    plt.show()
    
    画像
    画像
    画像
    画像

    🚨 トラブルシューティング

    よくあるエラーと対処法

    # エラーハンドリングを含む堅牢なバージョン
    class RobustColorExtractor(ColorPaletteExtractor):
        """
        エラーハンドリングを強化したバージョン
        """
        
        def extract_palette(self, image_input):
            """
            様々な入力形式に対応
            """
            try:
                # 入力の型を判定
                if isinstance(image_input, str):
                    # ファイルパスの場合
                    image = Image.open(image_input)
                elif isinstance(image_input, Image.Image):
                    # PIL画像の場合
                    image = image_input
                elif isinstance(image_input, np.ndarray):
                    # NumPy配列の場合
                    image = Image.fromarray(image_input)
                else:
                    raise ValueError("サポートされていない画像形式です")
                
                # RGBAからRGBに変換(透明度を除去)
                if image.mode == 'RGBA':
                    background = Image.new('RGB', image.size, (255, 255, 255))
                    background.paste(image, mask=image.split()[-1])
                    image = background
                elif image.mode != 'RGB':
                    image = image.convert('RGB')
                
                # 画像サイズチェック
                if image.size[0] * image.size[1] > 10000000:  # 1000万ピクセル以上
                    print("⚠️ 画像が大きいため、リサイズします...")
                    image.thumbnail((2000, 2000), Image.Resampling.LANCZOS)
                
                # 元の処理を実行
                return super().extract_palette(image)
                
            except FileNotFoundError:
                print("❌ エラー: ファイルが見つかりません")
            except PIL.UnidentifiedImageError:
                print("❌ エラー: サポートされていない画像ファイル形式です")
            except MemoryError:
                print("❌ エラー: メモリ不足です。画像サイズを小さくしてください")
            except Exception as e:
                print(f"❌ 予期しないエラー: {str(e)}")
            
            return None
    
    # 使用例とテスト
    def test_error_handling():
        """
        エラーハンドリングのテスト
        """
        extractor = RobustColorExtractor()
        
        print("🔧 エラーハンドリングテスト:")
        
        # 正常ケース
        sample_img = np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8)
        result = extractor.extract_palette(sample_img)
        if result:
            print("✅ NumPy配列からの抽出: 成功")
        
        # PIL画像
        pil_img = Image.fromarray(sample_img)
        result = extractor.extract_palette(pil_img)
        if result:
            print("✅ PIL画像からの抽出: 成功")
        
        # 存在しないファイル
        result = extractor.extract_palette("存在しないファイル.jpg")
        
        print("🔧 テスト完了")
    
    test_error_handling()
    

    パフォーマンス最適化のコツ

    def performance_tips():
        """
        パフォーマンス向上のためのTips
        """
        print("⚡ パフォーマンス最適化のコツ:")
        print("-" * 50)
        print("1. 大きな画像は事前にリサイズ")
        print("   image.thumbnail((1000, 1000)) など")
        print()
        print("2. max_pixelsパラメータを調整")
        print("   精度 vs 速度のバランスを調整")
        print()
        print("3. 色数を適切に設定")
        print("   用途に応じて3-7色程度が最適")
        print()
        print("4. バッチ処理時はメモリ管理に注意")
        print("   大量の画像を処理する際は一定間隔でガベージコレクション")
        print("-" * 50)
    
    performance_tips()
    
    # メモリ効率化例
    import gc
    
    def process_large_batch(image_list):
        """
        大量画像の効率的な処理
        """
        results = []
        
        for i, img in enumerate(image_list):
            extractor = ColorPaletteExtractor(max_pixels=5000)  # ピクセル数制限
            result = extractor.extract_palette(img)
            results.append(result)
            
            # 10枚ごとにメモリクリーンアップ
            if (i + 1) % 10 == 0:
                gc.collect()
                print(f"✅ {i+1}枚処理完了")
        
        return results
    

    🔮 発展課題・応用アイデア

    1. 動画ファイル対応

    # 動画からキーフレームを抽出してカラーパレット生成
    def extract_video_palette_concept():
        """
        動画対応の概念実装
        注意: 実際にはOpenCVやffmpegが必要
        """
        print("🎬 動画対応の実装アイデア:")
        print("-" * 40)
        print("1. フレーム抽出:")
        print("   import cv2")
        print("   cap = cv2.VideoCapture('video.mp4')")
        print("   # N秒間隔でフレームを取得")
        print()
        print("2. タイムラプス風パレット:")
        print("   時間経過と共に色が変化する様子を可視化")
        print()
        print("3. シーン検出:")
        print("   色の変化からシーンの切り替わりを検出")
        print("-" * 40)
    
    extract_video_palette_concept()
    

    2. リアルタイム処理

    def realtime_processing_concept():
        """
        リアルタイム処理のアイデア
        """
        print("📹 リアルタイム処理の実装アイデア:")
        print("-" * 40) 
        print("1. Webカメラ連携:")
        print("   カメラの映像からリアルタイムで色抽出")
        print()
        print("2. 配信アプリ連携:")
        print("   OBS等の配信ソフトウェアと連携")
        print()
        print("3. IoT活用:")
        print("   Raspberry Piでカメラ付きカラーセンサー")
        print("-" * 40)
    
    realtime_processing_concept()
    

    3. Web API化

    def web_api_concept():
        """
        Web API化のアイデア
        """
        print("🌐 Web API化のアイデア:")
        print("-" * 40)
        print("1. FastAPI/Flaskでの実装:")
        print("   POST /extract_colors")
        print("   画像アップロード → JSON形式でパレット返却")
        print()
        print("2. 想定レスポンス:")
        print('   {"colors": [{"hex": "#FF5733", "percentage": 25.5}]}')
        print()
        print("3. 料金設定例:")
        print("   個人利用: 無料(月100リクエスト)")
        print("   商用利用: 月額500円(月10,000リクエスト)")
        print("-" * 40)
    
    web_api_concept()
    

    🎉 完成!全機能のデモ

    # 完全版デモ: 全機能を統合したサンプル(修正版)
    def final_demo():
        """
        完成版アプリケーションの総合デモ
        """
        print("🎉 カラーパレット抽出アプリ - 完成版デモ")
        print("=" * 60)
        
        # テスト用画像生成(カラフルなアート風)
        demo_img = np.zeros((300, 400, 3), dtype=np.uint8)
        
        # 複数の色領域を作成(より大きな領域で確実に色が抽出されるように)
        regions = [
            # (x1, y1, x2, y2, RGB色)
            (0, 0, 200, 150, [255, 99, 71]),    # トマト色(左上)
            (200, 0, 400, 150, [60, 179, 113]), # ミディアムシーグリーン(右上)
            (0, 150, 130, 300, [30, 144, 255]), # ドジャーブルー(左下)
            (130, 150, 270, 300, [255, 215, 0]), # ゴールド(中央下)
            (270, 150, 400, 300, [186, 85, 211])  # ミディアムオーキッド(右下)
        ]
        
        print("🖼️ カラフルなデモ画像を作成中...")
        for x1, y1, x2, y2, color in regions:
            demo_img[y1:y2, x1:x2] = color
            print(f"  領域 ({x1},{y1})-({x2},{y2}): RGB{color}")
        
        demo_pil = Image.fromarray(demo_img)
        
        # 作成された画像を表示
        plt.figure(figsize=(10, 6))
        plt.imshow(demo_pil)
        plt.title("デモ用画像(5色の明確な領域)", fontsize=16)
        plt.axis('off')
        plt.show()
        
        # 全機能実行(修正版ColorPaletteExtractorを使用)
        print("🔍 Step 1: 高精度色抽出実行中...")
        try:
            extractor = ColorPaletteExtractor(n_colors=5)
            result = extractor.extract_palette(demo_pil)
            
            if result:
                print("🎨 Step 2: パレット可視化...")
                extractor.visualize_palette(show_pie_chart=True)
                
                print("📊 Step 3: 詳細レポート出力...")
                extractor.print_report()
                
                print("🔬 Step 4: 色彩調和分析...")
                if extractor.colors is not None:
                    try:
                        harmony_type = analyze_color_harmony(extractor.colors)
                    except Exception as e:
                        print(f"⚠️ 色彩調和分析でエラー: {e}")
                
                print("💡 Step 5: 活用提案...")
                try:
                    suggest_color_applications(extractor)
                except Exception as e:
                    print(f"⚠️ 活用提案でエラー: {e}")
                
                print("💾 Step 6: ファイル保存...")
                try:
                    filename = extractor.save_palette("demo_palette.png")
                    if filename:
                        print(f"✅ {filename}に保存完了")
                except Exception as e:
                    print(f"⚠️ ファイル保存でエラー: {e}")
                
                print("🎯 Step 7: CSS出力...")
                try:
                    css_vars = extractor.get_css_variables()
                    if css_vars:
                        print("\n/* 生成されたCSS変数 */")
                        print(css_vars)
                    else:
                        print("CSS変数の生成に失敗しました")
                except Exception as e:
                    print(f"⚠️ CSS出力でエラー: {e}")
                
                print("\n" + "=" * 60)
                print("🎉 デモ完了!全機能が正常に動作しました。")
                print("💰 このレベルのツールを500円で習得できるのはお得ですね!")
                
                # 追加:実用性の確認
                print("\n🔍 実用性チェック:")
                if extractor.color_info:
                    unique_colors = set()
                    for info in extractor.color_info:
                        unique_colors.add(info['rgb'])
                    
                    if len(unique_colors) >= 4:
                        print("✅ 4色以上の異なる色が抽出されました")
                        print("✅ 実用的なカラーパレットとして使用可能です")
                    else:
                        print("⚠️ 抽出された色の種類が少ないです")
                
                print("=" * 60)
                
            else:
                print("❌ 色抽出に失敗しました")
                
        except Exception as e:
            print(f"❌ デモ実行中にエラーが発生: {e}")
            import traceback
            traceback.print_exc()
    
    # 色彩調和分析とアプリケーション提案の簡易版(エラー対応)
    def analyze_color_harmony_simple(colors):
        """
        色彩調和分析(簡易版・エラー対応)
        """
        try:
            import colorsys
            
            print("🎨 色彩調和分析:")
            print("-" * 40)
            
            hsv_colors = []
            for i, color in enumerate(colors):
                try:
                    r, g, b = [x/255.0 for x in color]
                    h, s, v = colorsys.rgb_to_hsv(r, g, b)
                    hsv_colors.append((h*360, s, v))
                    
                    # 色相に基づく分類
                    hue = h * 360
                    if hue < 30 or hue >= 330:
                        color_family = "赤系"
                    elif hue < 90:
                        color_family = "黄系"
                    elif hue < 150:
                        color_family = "緑系"
                    elif hue < 210:
                        color_family = "青系"
                    elif hue < 270:
                        color_family = "紫系"
                    else:
                        color_family = "マゼンタ系"
                    
                    print(f"色{i+1}: H={hue:.0f}° S={s:.2f} V={v:.2f} ({color_family})")
                    
                except Exception as e:
                    print(f"色{i+1}: 分析エラー - {e}")
            
            # 色相の分布分析
            if len(hsv_colors) > 0:
                hues = [hsv[0] for hsv in hsv_colors]
                hue_range = max(hues) - min(hues)
                
                print(f"\n📊 色相分析:")
                print(f"色相の範囲: {hue_range:.1f}°")
                
                if hue_range < 30:
                    harmony_type = "類似色調和(Analogous)"
                elif hue_range < 60:
                    harmony_type = "近似類似色調和"
                elif 150 < hue_range < 210:
                    harmony_type = "補色調和(Complementary)"
                else:
                    harmony_type = "多色調和(Polychromatic)"
                
                print(f"調和タイプ: {harmony_type}")
                return harmony_type
            
        except ImportError:
            print("⚠️ colorsysライブラリが利用できません")
        except Exception as e:
            print(f"⚠️ 色彩調和分析エラー: {e}")
        
        return "分析不可"
    
    def suggest_color_applications_simple(extractor):
        """
        色の特徴に基づいて用途を提案(簡易版・エラー対応)
        """
        try:
            if extractor.color_info is None:
                print("⚠️ 色情報が取得できていません")
                return
            
            print("\n💡 カラーパレット活用提案:")
            print("=" * 50)
            
            # 色数による提案
            color_count = len(extractor.colors)
            if color_count <= 3:
                print("🎯 ミニマル → ロゴデザイン、シンプルなUI")
            elif color_count <= 5:
                print("🎨 バランス良好 → Webサイト、ポスターデザイン")
            else:
                print("🌈 リッチ → 複雑なデザイン、アート作品")
            
            # 基本的な用途提案
            print("🌟 基本的な活用例:")
            print("  • Webサイトのカラースキーム")
            print("  • SNS投稿の統一感演出")
            print("  • プレゼン資料の配色")
            print("  • イラスト・デザイン制作の参考")
            
            print("=" * 50)
            
        except Exception as e:
            print(f"⚠️ 活用提案エラー: {e}")
    
    # グローバル関数として定義
    analyze_color_harmony = analyze_color_harmony_simple
    suggest_color_applications = suggest_color_applications_simple
    
    # 最終デモ実行
    print("🚀 完全デモを開始します...")
    final_demo()
    画像
    STEP1
    画像
    STEP2
    画像
    STEP3 ~ STEP6
    画像
    STEP7

    この教材では以下を学習しました

    ✅ 基礎から応用まで体系的な画像解析技術
    ✅ 機械学習(K-means)の実践的な活用法
    ✅ 実用的なカラーパレット生成アプリ
    ✅ プロダクト品質のエラーハンドリング
    ✅ ビジネス活用までの具体的なロードマップ


    🙏 最後に

    画像解析はAI・データサイエンス分野の入り口でもあります。

    • 画像認識システム

    • 自動タグ付けツール

    • 商品推薦システム

    • 品質管理システム

    など、様々な高度なアプリケーションへ発展させることができます。

    今日学んだ技術が、あなたの未来を変える第一歩になることを願っています! 🌟


    この教材が役に立ったら、ぜひレビューやシェアをお願いします。
    質問があれば、コメント欄でお気軽にお聞きください!

    Happy Coding! 🎨✨

     
     
    ほぼ毎週noteを投稿しています。 通知が来るから見たら絶対フォロー! 🏫 | 青山学院大学(理工学部数学科) ✅ | Notion公式キャンパスリーダー 🚀 | Notion Students Japan 運営 💻 | 生成AI, プログラミング

    あなたへのおすすめ