# バウアーの投球のクセ？をネタにpython勉強(備忘録)

By [Shogaku](https://paragraph.com/@shogaku) · 2023-05-23

---

バウアーファンとしては、打ち込まれたのがショックでした。。

これは、、、クセが見抜かれていたのか

気になってきたので、自分でも見てみようとpythonの勉強がてら画像を見てみた

後ろからの画像しか持っていないので、参考程度に。

* * *

Google ColabとGoogle Driveを接続
----------------------------

Google Colaboratory（別名Google Colab）とGoogle Driveを接続するためのものです。Google Colabは、クラウドベースのJupyterノートブック環境であり、Google Driveは、ファイル保存と共有のためのクラウドストレージサービスです。

    # まず、Google ColabとGoogle Driveを接続します。以下のコードを使用します。
    from google.colab import drive
    
    drive.mount('/content/drive')
    

* * *

すべてのファイル名を表示
------------

このPythonコードは、指定されたパス（ここでは '/content/drive/MyDrive/Colab Notebooks/test/2'）にあるフォルダが存在するかどうかを確認し、存在する場合にはその中のすべてのファイル名を表示します。

    import os
    
    folder_path = '/content/drive/MyDrive/Colab Notebooks/test/2　'あなたのパス名にしてください
    
    # フォルダが存在することを確認します
    if os.path.exists(folder_path):
        files = os.listdir(folder_path)
    
        for file_name in files:
            print(file_name)
    else:
        print(f"The folder does not exist: {folder_path}")
    

ちなみにこんな画像

![](https://storage.googleapis.com/papyrus_images/91a442f049f224d1e388f1c0c6ddc82f64ff7eaed1377c5eb78dcfd1e0bbfe2d.png)

* * *

2つの画像間の差分を計算し、その差分がある場所（ピクセル）の数を表示
----------------------------------

このPythonコードは、2つの画像間の差分を計算し、その差分がある場所（ピクセル）の数を表示します。

    from PIL import Image
    import numpy as np
    
    # 画像を読み込む
    img1 = Image.open('/content/drive/MyDrive/Colab Notebooks/test/2/17-KC.png')
    img2 = Image.open('/content/drive/MyDrive/Colab Notebooks/test/2/18-FF.png')
    
    # 画像をnumpy配列に変換
    img1_arr = np.array(img1)
    img2_arr = np.array(img2)
    
    # 画像の差分を計算
    diff_arr = img1_arr - img2_arr
    
    # 差分がある箇所を抽出（差分が0より大きい場所）
    diff_points = np.where(diff_arr > 0)
    
    # 差分がある場所の数を表示
    print(len(diff_points[0]))
    

結果、

* * *

2つの画像間の差分を視覚化
-------------

このPythonコードは2つの画像間の差分を視覚化します。それぞれの画像を読み込み、配列に変換し、その差分を計算します。そして、差分が存在する箇所を抽出し、新たに生成した透明な画像上に白色で表示します。その結果、2つの画像間で異なる箇所が白色で視覚化されます。

    import matplotlib.pyplot as plt
    
    # 画像を読み込む
    img1 = Image.open('/content/drive/MyDrive/Colab Notebooks/test/2/17-KC.png')
    img2 = Image.open('/content/drive/MyDrive/Colab Notebooks/test/2/18-FF.png')
    
    # 画像をnumpy配列に変換
    img1_arr = np.array(img1)
    img2_arr = np.array(img2)
    
    # RGBAの各チャンネルの差分を計算
    diff_arr = np.abs(img1_arr - img2_arr)
    
    # 差分がある箇所を抽出（差分が0より大きい場所）
    # 全てのチャンネルについて差分が0より大きい場所を抽出
    diff_points = np.where(np.any(diff_arr > 0, axis=-1))
    
    # 差分を視覚化するための空の画像を作成（透明度のためのアルファチャンネルも含む）
    diff_img = np.zeros_like(img1_arr)
    
    # 差分がある箇所を白色（255, 255, 255, 255）で塗りつぶす
    diff_img[diff_points] = [255, 255, 255, 255]
    
    # 差分画像を表示
    plt.imshow(diff_img)
    plt.show()
    

![](https://storage.googleapis.com/papyrus_images/94510925dff41e30323b663a0196871283bea3d8209064f6f1edb8a9f95be256.png)

* * *

2つの画像間の差分を赤色で視覚化
----------------

このPythonコードは、2つの画像間の差分を赤色で視覚化し、その差分を元の画像に重ねて表示します。

    # 画像を読み込む
    img1 = Image.open('/content/drive/MyDrive/Colab Notebooks/test/2/17-KC.png').convert('RGBA')
    img2 = Image.open('/content/drive/MyDrive/Colab Notebooks/test/2/18-FF.png').convert('RGBA')
    
    # 画像をnumpy配列に変換
    img1_arr = np.array(img1)
    img2_arr = np.array(img2)
    
    # RGBAの各チャンネルの差分を計算
    diff_arr = np.abs(img1_arr - img2_arr)
    
    # 差分がある箇所を抽出（差分が0より大きい場所）
    # 全てのチャンネルについて差分が0より大きい場所を抽出
    diff_points = np.where(np.any(diff_arr > 0, axis=-1))
    
    # 差分を視覚化するための空の画像を作成（透明度のためのアルファチャンネルも含む）
    diff_img = np.zeros_like(img1_arr)
    
    # 差分がある箇所を赤色（255, 0, 0, 255）で塗りつぶす
    diff_img[diff_points] = [255, 0, 0, 255]
    
    # 差分画像をPILのImageオブジェクトに変換
    diff_img_pil = Image.fromarray(diff_img)
    
    # 元の画像と差分画像を合成
    composite = Image.alpha_composite(img1, diff_img_pil)
    
    # 合成画像を表示
    plt.imshow(composite)
    plt.show()
    

![](https://storage.googleapis.com/papyrus_images/859fecdf898e27ac1e77d229707c9ea920b9004b7a433f6d995c322bece6b232.png)

* * *

背景を透明にします
---------

このPythonコードは、画像から特定の色範囲（ここでは緑色）を透明化する関数transparent\_backgroundを定義し、2つの画像に対してその関数を適用して背景を透明にします。

    def transparent_background(image, lower_green, upper_green):
        """
        画像の背景を透明にする関数
    
        Parameters
        ----------
        image: PIL.Image
            入力画像（RGBA）
        lower_green: array_like
            緑色の下限（[R, G, B]）
        upper_green: array_like
            緑色の上限（[R, G, B]）
    
        Returns
        -------
        image: PIL.Image
            背景が透明化された画像
        """
        # 画像をnumpy配列に変換
        img_arr = np.array(image)
    
        # RGBの各チャンネルの値が指定の緑色の範囲内（背景と判定）の箇所を抽出
        bg_points = np.where(((img_arr[:, :, :3] >= lower_green) & (img_arr[:, :, :3] <= upper_green)).all(axis=-1))
    
        # 背景と判定された箇所を透明（0, 0, 0, 0）にする
        img_arr[bg_points] = [0, 0, 0, 0]
    
        # numpy配列をPILのImageオブジェクトに戻す
        return Image.fromarray(img_arr)
    
    # 画像を読み込みRGBAに変換
    img1 = Image.open('/content/drive/MyDrive/Colab Notebooks/test/2/17-KC.png').convert('RGBA')
    img2 = Image.open('/content/drive/MyDrive/Colab Notebooks/test/2/18-FF.png').convert('RGBA')
    
    # 背景色の緑色の範囲を定義
    lower_green = np.array([0, 120, 0])  # このRGB値以上を背景色と判定
    upper_green = np.array([150, 255, 150])  # このRGB値以下を背景色と判定
    
    # 背景を透明化
    img1_trans = transparent_background(img1, lower_green, upper_green)
    img2_trans = transparent_background(img2, lower_green, upper_green)
    
    # （以降、img1_transとimg2_transを用いて差分の計算と表示を行う）
    

* * *

差分を元の透明化された画像に重ねて表示
-------------------

このPythonコードは、背景を透明化した2つの画像間の差分を赤色で視覚化し、その差分を元の透明化された画像に重ねて表示します

    # 画像をnumpy配列に変換
    img1_trans_arr = np.array(img1_trans)
    img2_trans_arr = np.array(img2_trans)
    
    # RGBAの各チャンネルの差分を計算
    diff_trans_arr = np.abs(img1_trans_arr - img2_trans_arr)
    
    # 差分がある箇所を抽出（差分が0より大きい場所）
    # 全てのチャンネルについて差分が0より大きい場所を抽出
    diff_points = np.where(np.any(diff_trans_arr > 0, axis=-1))
    
    # 差分を視覚化するための空の画像を作成（透明度のためのアルファチャンネルも含む）
    diff_img = np.zeros_like(img1_trans_arr)
    
    # 差分がある箇所を赤色（255, 0, 0, 255）で塗りつぶす
    diff_img[diff_points] = [255, 0, 0, 255]
    
    # 差分画像をPILのImageオブジェクトに変換
    diff_img_pil = Image.fromarray(diff_img)
    
    # 元の画像（背景透明化済み）と差分画像を合成
    composite = Image.alpha_composite(img1_trans, diff_img_pil)
    
    # 合成画像を表示
    plt.imshow(composite)
    plt.show()
    

![](https://storage.googleapis.com/papyrus_images/165be3f3cd2e5226ed65093e316c3a6b511980312e967727dcfdbf14f309b1ce.png)

* * *

一定のしきい値（この例では250）を超えるピクセル（極端な変化があった場所）を赤色で強調
--------------------------------------------

このコードも2つの画像間の差分を視覚化するためのものですが、ここではImageChops.difference関数を使用して差分を直接計算し、その後で差分の絶対値が一定のしきい値（この例では250）を超えるピクセル（極端な変化があった場所）を赤色で強調しています。

    from PIL import ImageChops
    
    # 画像の差分を取得
    diff = ImageChops.difference(img1_trans, img2_trans)
    
    # 差分画像をNumpy配列に変換
    diff_arr = np.array(diff)
    
    # RGBの各チャンネルで差の絶対値が一定のしきい値（ここでは250）を超えるピクセルを抽出
    threshold = 250
    extreme_points = np.where(np.any(diff_arr[:, :, :3] > threshold, axis=-1))
    
    # 強調したい箇所を赤色（255, 0, 0, 255）で塗りつぶす
    highlight_img_arr = np.zeros_like(img1_trans_arr)
    highlight_img_arr[extreme_points] = [255, 0, 0, 255]
    
    # 強調画像をPILのImageオブジェクトに変換
    highlight_img_pil = Image.fromarray(highlight_img_arr)
    
    # 元の画像（背景透明化済み）と強調画像を合成
    highlight_composite = Image.alpha_composite(img1_trans, highlight_img_pil)
    
    # 合成画像を表示
    plt.imshow(highlight_composite)
    plt.show()
    

![](https://storage.googleapis.com/papyrus_images/367b6afcc34d25bd14e22fa19329e54f7cad5a75c5587cc95f9736d61b03f220.png)

* * *

画像の差分が大きい箇所をK-meansクラスタリングを用いて分類し、その結果を円で表示
-------------------------------------------

このコードは、画像の差分が大きい箇所をK-meansクラスタリングを用いて分類し、その結果を円で表示するものです。K-meansクラスタリングとは、データをK個のクラスタに分割するためのアルゴリズムです。この例では、ピクセル位置のデータに対してクラスタリングを行っています。

    import matplotlib.patches as patches
    from sklearn.cluster import KMeans
    
    # クラスタの数（エリアの数）を設定
    n_clusters = 5
    
    # 差分が大きいピクセルの位置を取得
    extreme_points_array = np.array(extreme_points).transpose()
    
    # K-means クラスタリングを適用
    kmeans = KMeans(n_clusters=n_clusters)
    kmeans.fit(extreme_points_array)
    
    # 各クラスタの中心を取得
    centers = kmeans.cluster_centers_
    
    # 描画用の新しいfigとaxを作成
    fig, ax = plt.subplots(1)
    
    # 画像を表示
    ax.imshow(highlight_composite)
    
    extreme_radius = 20  # 半径を設定
    
    # 各クラスタの中心に円を描画
    for center in centers:
        circle = patches.Circle((center[1], center[0]), extreme_radius, edgecolor='blue', facecolor='none', linewidth=2)
        ax.add_patch(circle)
    
    # 描画
    plt.show()
    

![](https://storage.googleapis.com/papyrus_images/0220f4e2634b876e427ee1fa576be09b5b815e7e5cc5e875833124b41d3a6633.png)

* * *

半径20ピクセルの円を描くことで、クラスタリングの結果を可視化
-------------------------------

このコードブロックでは、前のステップで取得した各クラスタの中心点に、半径20ピクセルの円を描くことで、クラスタリングの結果を可視化しています。

    # 描画用の新しいfigとaxを作成
    fig, ax = plt.subplots(1)
    
    # 画像を表示
    ax.imshow(highlight_composite)
    
    # 各クラスタの中心に小さな円を描画
    circle_radius = 20  # 円の半径を20ピクセルに設定
    for center in centers:
        circle = patches.Circle((center[1], center[0]), circle_radius, edgecolor='blue', facecolor='none', linewidth=2)
        ax.add_patch(circle)
    
    # 描画
    plt.show()
    

![](https://storage.googleapis.com/papyrus_images/0220f4e2634b876e427ee1fa576be09b5b815e7e5cc5e875833124b41d3a6633.png)

* * *

すべての画像ファイルに対して背景の透明化、画像の差分計算、強調エリアのクラスタリング、そして結果の可視化
----------------------------------------------------

このコードブロックでは、特定のフォルダ内のすべての画像ファイルに対して背景の透明化、画像の差分計算、強調エリアのクラスタリング、そして結果の可視化を行います。

    import os
    from PIL import Image, ImageChops
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.cluster import KMeans
    from matplotlib import patches
    
    def transparent_background(image, lower_green, upper_green):
        img = image.copy()
        img = img.convert("RGBA")
        datas = img.getdata()
    
        newData = []
        for item in datas:
            if item[0] in list(range(lower_green[0], upper_green[0])) and item[1] in list(range(lower_green[1], upper_green[1])) and item[2] in list(range(lower_green[2], upper_green[2])):
                newData.append((255, 255, 255, 0))
            else:
                newData.append(item)
    
        img.putdata(newData)
        return img
    
    folder_path = '/content/drive/MyDrive/Colab Notebooks/test/2'
    
    # 緑色の範囲（RGB）を定義
    lower_green = (0, 120, 0)
    upper_green = (100, 255, 100)
    
    # クラスタ数を定義（ここでは4とします）
    n_clusters = 4
    
    # 小さな円の半径を定義
    circle_radius = 20
    
    # フォルダ内のファイル名を取得
    file_names = os.listdir(folder_path)
    
    # 画像ファイルの拡張子
    valid_extensions = [".jpg", ".jpeg", ".png", ".bmp", ".gif"]
    
    for file_name in file_names:
        # ファイル名が有効な拡張子を持っているかどうかを確認
        if not any(file_name.endswith(ext) for ext in valid_extensions):
            continue
    
        img_path = os.path.join(folder_path, file_name)
    
    
        # 画像読み込み
        img = Image.open(img_path).convert('RGBA')
        
        # 背景透明化
        img_trans = transparent_background(img, lower_green, upper_green)
        
        # 背景透明化された画像をNumpy配列に変換
        img_trans_arr = np.array(img_trans)
    
        # 前の画像との差分を取得
        diff = ImageChops.difference(img_trans, img_trans_prev) if 'img_trans_prev' in locals() else img_trans
    
        # 差分画像をNumpy配列に変換
        diff_arr = np.array(diff)
    
        # RGBの各チャンネルで差の絶対値が一定のしきい値（ここでは250）を超えるピクセルを抽出
        threshold = 250
        extreme_points = np.where(np.any(diff_arr[:, :, :3] > threshold, axis=-1))
    
        # クラスタリングを適用
        extreme_points_array = np.array(extreme_points).transpose()
        
        if extreme_points_array.shape[0] > 0:  # 追加された条件チェック
            n_clusters_adj = min(n_clusters, extreme_points_array.shape[0])  # 調整されたn_clusters
            kmeans = KMeans(n_clusters=n_clusters_adj)
            kmeans.fit(extreme_points_array)
            centers = kmeans.cluster_centers_
    
            # 強調画像の作成
            highlight_img_arr = np.zeros_like(img_trans_arr)
            highlight_img_arr[extreme_points] = [255, 0, 0, 255]
            highlight_img_pil = Image.fromarray(highlight_img_arr)
            highlight_composite = Image.alpha_composite(img_trans, highlight_img_pil)
    
            # 描画用の新しいfigとaxを作成
            fig, ax = plt.subplots(1)
    
            # 画像を表示
            ax.imshow(highlight_composite)
    
            # 各クラスタの中心に小さな円を描画
            for center in centers:
                circle = patches.Circle((center[1], center[0]), circle_radius, edgecolor='blue', facecolor='none', linewidth=2)
                ax.add_patch(circle)
    
            # 描画
            plt.show()
            
        else:
            print(f"No significant difference found in image {file_name}. Skipping...")
    
        # 次回の差分計算のために現在の画像を保存
        img_trans_prev = img_trans
    

![](https://storage.googleapis.com/papyrus_images/242c42a016d6dd16600055409f87d41fcbf7ad7129b3977bbcae6c42c144a260.jpg)

* * *

K-meansアルゴリズム
-------------

このコードは前回のものと基本的に同じで、背景の透明化、画像の差分計算、強調エリアのクラスタリング、そして結果の可視化を行いますが、一つだけ新たに追加された部分があります。

それは、KMeans関数に渡す引数にn\_init=10が追加されています。n\_initはK-meansアルゴリズムの実行回数を指定します。K-meansアルゴリズムはランダムな初期化を行うため、実行するたびに結果が変わる可能性があります。そのため、n\_init=10は10回K-meansを実行し、そのうち最も良い結果（クラスタ間の距離が最小）を選択します。これにより、より安定したクラスタリング結果を得ることができます。

ただし、この変更により実行時間が増える可能性があります。それはK-meansアルゴリズムを複数回実行するためです。

    import os
    from PIL import Image, ImageChops
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.cluster import KMeans
    from matplotlib import patches
    
    def transparent_background(image, lower_green, upper_green):
        img = image.copy()
        img = img.convert("RGBA")
        datas = img.getdata()
    
        newData = []
        for item in datas:
            if item[0] in list(range(lower_green[0], upper_green[0])) and item[1] in list(range(lower_green[1], upper_green[1])) and item[2] in list(range(lower_green[2], upper_green[2])):
                newData.append((255, 255, 255, 0))
            else:
                newData.append(item)
    
        img.putdata(newData)
        return img
    
    folder_path = '/content/drive/MyDrive/Colab Notebooks/test/2'
    
    # 緑色の範囲（RGB）を定義
    lower_green = (0, 120, 0)
    upper_green = (100, 255, 100)
    
    # クラスタ数を定義（ここでは4とします）
    n_clusters = 4
    
    # 小さな円の半径を定義
    circle_radius = 20
    
    
    # フォルダ内のファイル名を取得
    file_names = os.listdir(folder_path)
    
    # 画像ファイルの拡張子
    valid_extensions = [".jpg", ".jpeg", ".png", ".bmp", ".gif"]
    
    for file_name in file_names:
        # ファイル名が有効な拡張子を持っているかどうかを確認
        if not any(file_name.endswith(ext) for ext in valid_extensions):
            continue
    
        img_path = os.path.join(folder_path, file_name)
    
        # 画像読み込み
        img = Image.open(img_path).convert('RGBA')
    
        
        # 背景透明化
        img_trans = transparent_background(img, lower_green, upper_green)
        
        # 背景透明化された画像をNumpy配列に変換
        img_trans_arr = np.array(img_trans)
    
        # 前の画像との差分を取得
        diff = ImageChops.difference(img_trans, img_trans_prev) if 'img_trans_prev' in locals() else img_trans
    
        # 差分画像をNumpy配列に変換
        diff_arr = np.array(diff)
    
        # RGBの各チャンネルで差の絶対値が一定のしきい値（ここでは250）を超えるピクセルを抽出
        threshold = 250
        extreme_points = np.where(np.any(diff_arr[:, :, :3] > threshold, axis=-1))
    
        # クラスタリングを適用
        extreme_points_array = np.array(extreme_points).transpose()
        
        if extreme_points_array.shape[0] > 0:  # 追加された条件チェック
            n_clusters_adj = min(n_clusters, extreme_points_array.shape[0])  # 調整されたn_clusters
            kmeans = KMeans(n_clusters=n_clusters_adj, n_init=10)
            kmeans.fit(extreme_points_array)
            centers = kmeans.cluster_centers_
    
            # 強調画像の作成
            highlight_img_arr = np.zeros_like(img_trans_arr)
            highlight_img_arr[extreme_points] = [255, 0, 0, 255]
            highlight_img_pil = Image.fromarray(highlight_img_arr)
            highlight_composite = Image.alpha_composite(img_trans, highlight_img_pil)
    
            # 描画用の新しいfigとaxを作成
            fig, ax = plt.subplots(1)
    
            # 画像を表示
            ax.imshow(highlight_composite)
    
            # 各クラスタの中心に小さな円を描画
            for center in centers:
                circle = patches.Circle((center[1], center[0]), circle_radius, edgecolor='blue', facecolor='none', linewidth=2)
                ax.add_patch(circle)
    
            # 描画
            plt.show()
            
        else:
            print(f"No significant difference found in image {file_name}. Skipping...")
    
        # 次回の差分計算のために現在の画像を保存
        img_trans_prev = img_trans
    

![](https://storage.googleapis.com/papyrus_images/eefb2828557e2e9b0b6adde71eb85592186a0b521cc28ca45063c98c197ad980.jpg)

* * *

各球種の最初のフレームとしてファストボール（FF）を選択し、その後の各球種との差異を確認します
-----------------------------------------------

このコードは、前回と同じく画像の背景を透明にして、それぞれの画像が前の画像とどの程度変化しているかを判定し、その変化を赤色で強調し、最終的に変化の多いエリアをクラスタリングして青い円で示すという流れは変わっていません。ただし、変化の判定と強調が行われる対象が変わっています。

前回までのコードでは、全ての画像が前の画像と比較されていましたが、新たなコードでは、同じ投球種に属する各画像がその投球種の最初の画像と比較されます。これにより、同じ投球種内での変化を比較的容易に把握できるようになりました。

ファイル名のパース（解析）により、投球種が取得され、それが後の比較対象の選択に利用されています。また、それぞれの投球種の最初のフレームは、デフォルト辞書 first\_frames に保存されます。この辞書の各キーは投球種、値はその投球種の最初のフレームの画像となります。

この変更により、各投球種の動きのパターンやその変化を見ることができ、これにより投球の特性をより深く理解することができます。

    import os
    from PIL import Image, ImageChops
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.cluster import KMeans
    from matplotlib import patches
    from collections import defaultdict
    
    def transparent_background(image, lower_green, upper_green):
        img = image.copy()
        img = img.convert("RGBA")
        datas = img.getdata()
    
        newData = []
        for item in datas:
            if item[0] in list(range(lower_green[0], upper_green[0])) and item[1] in list(range(lower_green[1], upper_green[1])) and item[2] in list(range(lower_green[2], upper_green[2])):
                newData.append((255, 255, 255, 0))
            else:
                newData.append(item)
    
        img.putdata(newData)
        return img
    
    folder_path = '/content/drive/MyDrive/Colab Notebooks/test/2'
    
    # 緑色の範囲（RGB）を定義
    lower_green = (0, 120, 0)
    upper_green = (100, 255, 100)
    
    # クラスタ数を定義（ここでは4とします）
    n_clusters = 4
    
    # 小さな円の半径を定義
    circle_radius = 20
    
    # フォルダ内のファイル名を取得
    file_names = os.listdir(folder_path)
    
    # ファーストボールのフレームを保存する辞書
    first_frames = defaultdict(lambda: None)
    
    # 画像ファイルの拡張子
    valid_extensions = [".jpg", ".jpeg", ".png", ".bmp", ".gif"]
    
    for file_name in file_names:
        # ファイル名が有効な拡張子を持っているかどうかを確認
        if not any(file_name.endswith(ext) for ext in valid_extensions):
            continue
    
        img_path = os.path.join(folder_path, file_name)
    
        # 画像読み込み
        img = Image.open(img_path).convert('RGBA')
        
        # 背景透明化
        img_trans = transparent_background(img, lower_green, upper_green)
        
        # ファイル名から球種を取得
        pitch_type = file_name.split('-')[1].split('.')[0]
        
        if first_frames[pitch_type] is None:
            # この球種の最初のフレームを保存
            first_frames[pitch_type] = img_trans
        else:
            # 保存されているフレームと現在のフレームとの差分を計算
            diff = ImageChops.difference(img_trans, first_frames[pitch_type])
            
            # 差分画像をNumpy配列に変換
            diff_arr = np.array(diff)
    
            # RGBの各チャンネルで差の絶対値が一定のしきい値（ここでは250）を超えるピクセルを抽出
            threshold = 250
            extreme_points = np.where(np.any(diff_arr[:, :, :3] > threshold, axis=-1))
    
            # クラスタリングを適用
            extreme_points_array = np.array(extreme_points).transpose()
        
            if extreme_points_array.shape[0] > 0:  # 追加された条件チェック
                n_clusters_adj = min(n_clusters, extreme_points_array.shape[0])  # 調整されたn_clusters
                kmeans = KMeans(n_clusters=n_clusters_adj, n_init=10)
                kmeans.fit(extreme_points_array)
                centers = kmeans.cluster_centers_
    
                # 強調画像の作成
                highlight_img_arr = np.zeros_like(img_trans_arr)
                highlight_img_arr[extreme_points] = [255, 0, 0, 255]
                highlight_img_pil = Image.fromarray(highlight_img_arr)
                highlight_composite = Image.alpha_composite(img_trans, highlight_img_pil)
    
                # 描画用の新しいfigとaxを作成
                fig, ax = plt.subplots(1)
    
                # 画像を表示
                ax.imshow(highlight_composite)
    
                # 各クラスタの中心に小さな円を描画
                for center in centers:
                    circle = patches.Circle((center[1], center[0]), circle_radius, edgecolor='blue', facecolor='none', linewidth=2)
                    ax.add_patch(circle)
    
                # 描画
                plt.show()
                
            else:
                print(f"No significant difference found in image {file_name}. Skipping...")
    

![](https://storage.googleapis.com/papyrus_images/f7628b2fdac25c2c386b503d66b1106e68341e4226c8b880def43ff8b1038102.jpg)

* * *

生成される各画像にタイトルを追加
----------------

このコードは前述のコードと非常に似ており、画像の変化を検出し、変化の多いエリアを赤で強調、そしてそれらのエリアをクラスタリングし、青い円で表示するという処理の流れは同じです。

しかし、以下の修正点があります。

1.  ファイル名のリストをソートしています(sorted(file\_names))。これにより、ファイルがアルファベット順に処理されます。これは特にファイル名が一連の番号や日付で構成されている場合、処理の順序が重要となる場合に有用です。
    
2.  画像ファイルかどうかのチェックを少しシンプルにし、直接拡張子で確認しています。
    
3.  生成される各画像にタイトルを追加しています。これにより、各画像がどのファイルから生成され、どの投球種に関連しているかを一目で理解できます(plt.title(f"Image: {file\_name}, Pitch type: {pitch\_type}")).
    

以上の修正により、コードの理解が容易になり、結果の可視化もより情報が豊富になりました。

    import os
    from PIL import Image, ImageChops
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.cluster import KMeans
    from matplotlib import patches
    from collections import defaultdict
    
    def transparent_background(image, lower_green, upper_green):
        img = image.copy()
        img = img.convert("RGBA")
        datas = img.getdata()
    
        newData = []
        for item in datas:
            if item[0] in list(range(lower_green[0], upper_green[0])) and item[1] in list(range(lower_green[1], upper_green[1])) and item[2] in list(range(lower_green[2], upper_green[2])):
                newData.append((255, 255, 255, 0))
            else:
                newData.append(item)
    
        img.putdata(newData)
        return img
    
    folder_path = '/content/drive/MyDrive/Colab Notebooks/test/2'
    
    # 緑色の範囲（RGB）を定義
    lower_green = (0, 120, 0)
    upper_green = (100, 255, 100)
    
    # クラスタ数を定義（ここでは4とします）
    n_clusters = 4
    
    # 小さな円の半径を定義
    circle_radius = 20
    
    # フォルダ内のファイル名を取得
    file_names = os.listdir(folder_path)
    
    # ファーストボールのフレームを保存する辞書
    first_frames = defaultdict(lambda: None)
    
    for file_name in sorted(file_names):
        # Check if file is an image
        if not file_name.endswith(('.png', '.jpg', '.jpeg')):
            continue
    
        img_path = os.path.join(folder_path, file_name)
        
        # 画像読み込み
        img = Image.open(img_path).convert('RGBA')
        
        # 背景透明化
        img_trans = transparent_background(img, lower_green, upper_green)
        
        # ファイル名から球種を取得
        pitch_type = file_name.split('-')[1].split('.')[0]
        
        if first_frames[pitch_type] is None:
            # この球種の最初のフレームを保存
            first_frames[pitch_type] = img_trans
        else:
            # 保存されているフレームと現在のフレームとの差分を計算
            diff = ImageChops.difference(img_trans, first_frames[pitch_type])
            
            # 差分画像をNumpy配列に変換
            diff_arr = np.array(diff)
    
            # RGBの各チャンネルで差の絶対値が一定のしきい値（ここでは250）を超えるピクセルを抽出
            threshold = 250
            extreme_points = np.where(np.any(diff_arr[:, :, :3] > threshold, axis=-1))
    
            # クラスタリングを適用
            extreme_points_array = np.array(extreme_points).transpose()
        
            if extreme_points_array.shape[0] > 0:  # 追加された条件チェック
                n_clusters_adj = min(n_clusters, extreme_points_array.shape[0])  # 調整されたn_clusters
                kmeans = KMeans(n_clusters=n_clusters_adj, n_init=10)
                kmeans.fit(extreme_points_array)
                centers = kmeans.cluster_centers_
    
                # 強調画像の作成
                highlight_img_arr = np.zeros_like(img_trans_arr)
                highlight_img_arr[extreme_points] = [255, 0, 0, 255]
                highlight_img_pil = Image.fromarray(highlight_img_arr)
                highlight_composite = Image.alpha_composite(img_trans, highlight_img_pil)
    
                # 描画用の新しいfigとaxを作成
                fig, ax = plt.subplots(1)
    
                # 画像のタイトルを追加
                plt.title(f"Image: {file_name}, Pitch type: {pitch_type}")
    
                # 画像を表示
                ax.imshow(highlight_composite)
    
                # 各クラスタの中心に小さな円を描画
                for center in centers:
                    circle = patches.Circle((center[1], center[0]), circle_radius, edgecolor='blue', facecolor='none', linewidth=2)
                    ax.add_patch(circle)
    
                # 描画
                plt.show()
                
            else:
                print(f"No significant difference found in image {file_name}. Skipping...")
    

![](https://storage.googleapis.com/papyrus_images/a4131bb35986f6680abbf21a1563e0c91399a0f142e0171c29c19cf05001018d.png)

* * *

各ピッチタイプごとに最大4つの画像を表示する
----------------------

このコードでは、ピッチタイプごとに複数の画像を一度に表示するための変更が加えられています。具体的には、ピッチタイプごとに画像とクラスタセンターのリストを保持し、後で一度に表示するようにしています。

新しく追加された重要なコード部分は以下の通りです。

1.  各ピッチタイプの画像を保存する辞書(images\_per\_pitch\_type)を作成しました。各ピッチタイプに対応する値は、そのピッチタイプの画像とクラスタセンターのタプルのリストです。
    
2.  images\_per\_pitch\_type\[pitch\_type\].append((highlight\_composite, centers))で、ピッチタイプごとに強調表示された画像とクラスタセンターのタプルを追加しています。
    
3.  最後に、各ピッチタイプごとに最大4つの画像を表示するためのコードを追加しています。各サブプロット(axs\[i\])に画像を表示し、クラスタセンターに対応する円を描画します。
    

この変更により、ピッチタイプごとに一度に複数の画像を表示できるようになり、ピッチタイプごとの変化を視覚的に比較するのに役立ちます。ただし、各ピッチタイプの画像が多すぎる場合（4つ以上存在する場合）、最初の4つの画像だけが表示され、残りの画像は無視されます。この数値は必要に応じて調整可能です。

    import os
    from PIL import Image, ImageChops
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.cluster import KMeans
    from matplotlib import patches
    from collections import defaultdict
    
    def transparent_background(image, lower_green, upper_green):
        img = image.copy()
        img = img.convert("RGBA")
        datas = img.getdata()
    
        newData = []
        for item in datas:
            if item[0] in list(range(lower_green[0], upper_green[0])) and item[1] in list(range(lower_green[1], upper_green[1])) and item[2] in list(range(lower_green[2], upper_green[2])):
                newData.append((255, 255, 255, 0))
            else:
                newData.append(item)
    
        img.putdata(newData)
        return img
    
    folder_path = '/content/drive/MyDrive/Colab Notebooks/test/2'
    
    # 緑色の範囲（RGB）を定義
    lower_green = (0, 120, 0)
    upper_green = (100, 255, 100)
    
    # クラスタ数を定義（ここでは4とします）
    n_clusters = 4
    
    # 小さな円の半径を定義
    circle_radius = 20
    
    # フォルダ内のファイル名を取得
    file_names = os.listdir(folder_path)
    
    # ファーストボールのフレームを保存する辞書
    first_frames = defaultdict(lambda: None)
    
    # 各ピッチタイプの画像を保存する辞書
    images_per_pitch_type = defaultdict(list)
    
    for file_name in sorted(file_names):
        # Check if file is an image
        if not file_name.endswith(('.png', '.jpg', '.jpeg')):
            continue
    
        img_path = os.path.join(folder_path, file_name)
        
        # 画像読み込み
        img = Image.open(img_path).convert('RGBA')
        
        # 背景透明化
        img_trans = transparent_background(img, lower_green, upper_green)
        
        # ファイル名から球種を取得
        pitch_type = file_name.split('-')[1].split('.')[0]
        
        if first_frames[pitch_type] is None:
            # この球種の最初のフレームを保存
            first_frames[pitch_type] = img_trans
        else:
            # 保存されているフレームと現在のフレームとの差分を計算
            diff = ImageChops.difference(img_trans, first_frames[pitch_type])
            
            # 差分画像をNumpy配列に変換
            diff_arr = np.array(diff)
    
            # RGBの各チャンネルで差の絶対値が一定のしきい値（ここでは250）を超えるピクセルを抽出
            threshold = 250
            extreme_points = np.where(np.any(diff_arr[:, :, :3] > threshold, axis=-1))
    
            # クラスタリングを適用
            extreme_points_array = np.array(extreme_points).transpose()
        
            if extreme_points_array.shape[0] > 0:  # 追加された条件チェック
                n_clusters_adj = min(n_clusters, extreme_points_array.shape[0])  # 調整されたn_clusters
                kmeans = KMeans(n_clusters=n_clusters_adj, n_init=10)
                kmeans.fit(extreme_points_array)
                centers = kmeans.cluster_centers_
    
                # 強調画像の作成
                highlight_img_arr = np.zeros_like(img_trans_arr)
                highlight_img_arr[extreme_points] = [255, 0, 0, 255]
                highlight_img_pil = Image.fromarray(highlight_img_arr)
                highlight_composite = Image.alpha_composite(img_trans, highlight_img_pil)
    
                # ピッチタイプ毎に画像を保存
                images_per_pitch_type[pitch_type].append((highlight_composite, centers))
                
            else:
                print(f"No significant difference found in image {file_name}. Skipping...")
    
    # 画像を表示
    for pitch_type, images in images_per_pitch_type.items():
        fig, axs = plt.subplots(1, 4, figsize=(20, 5))
        fig.suptitle(f'Pitch type: {pitch_type}')
    
        for i, (highlight_composite, centers) in enumerate(images):
            if i < 4:  # 最初の4枚だけ表示
                axs[i].imshow(highlight_composite)
    
                # 各クラスタの中心に小さな円を描画
                for center in centers:
                    circle = patches.Circle((center[1], center[0]), circle_radius, edgecolor='blue', facecolor='none', linewidth=2)
                    axs[i].add_patch(circle)
    

![](https://storage.googleapis.com/papyrus_images/75190f6dc981e6af8e86aa9809f72a02b229ccbc9aa1bbbd03c7d350c37f3769.jpg)

* * *

各ピッチタイプごと、行ごとに4つの画像を表示
----------------------

このコードは、ピッチタイプごとに多数の画像を表示するためにさらに修正されています。具体的な変更点は以下の通りです：

1.  各ピッチタイプの画像がどの程度多いかに関わらず、すべての画像を表示するようになりました。各ピッチタイプの画像が4つ未満の場合、残りのサブプロットは空白になります。
    
2.  行ごとに4つの画像を表示します。各ピッチタイプの画像が4つを超える場合、新しい行が作成され、その行に4つの画像が表示されます。これは、n\_rows = -(-len(images) // 4)という行で計算されます。これは、画像の数を4で割ったものを切り上げることで行数を計算しています。
    
3.  axs\[i\].axis('off')が追加されています。これは、特定のサブプロットに画像がない場合、そのサブプロットの軸を非表示にするためです。
    

これにより、ピッチタイプごとに全ての画像を表示することが可能になり、ピッチタイプ間の比較がさらに容易になります。ピッチタイプごとに一度に複数の画像を表示できるようになり、ピッチタイプごとの変化を視覚的に比較するのに役立ちます。ただし、各ピッチタイプの画像が多すぎる場合（4つ以上存在する場合）、最初の4つの画像だけが表示され、残りの画像は無視されます。この数値は必要に応じて調整可能です。

    import os
    from PIL import Image, ImageChops
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.cluster import KMeans
    from matplotlib import patches
    from collections import defaultdict
    
    def transparent_background(image, lower_green, upper_green):
        img = image.copy()
        img = img.convert("RGBA")
        datas = img.getdata()
    
        newData = []
        for item in datas:
            if item[0] in list(range(lower_green[0], upper_green[0])) and item[1] in list(range(lower_green[1], upper_green[1])) and item[2] in list(range(lower_green[2], upper_green[2])):
                newData.append((255, 255, 255, 0))
            else:
                newData.append(item)
    
        img.putdata(newData)
        return img
    
    folder_path = '/content/drive/MyDrive/Colab Notebooks/test/2'
    lower_green = (0, 120, 0)
    upper_green = (100, 255, 100)
    n_clusters = 4
    circle_radius = 20
    file_names = os.listdir(folder_path)
    first_frames = defaultdict(lambda: None)
    images_per_pitch_type = defaultdict(lambda: [])
    
    for file_name in sorted(file_names):
        # Add this line to check file extension
        if not os.path.splitext(file_name)[1] in [".png", ".jpg", ".jpeg"]:
            continue
        
        img_path = os.path.join(folder_path, file_name)
        img = Image.open(img_path).convert('RGBA')
        
        img_trans = transparent_background(img, lower_green, upper_green)
        pitch_type = file_name.split('-')[1].split('.')[0]
    
        if first_frames[pitch_type] is None:
            first_frames[pitch_type] = img_trans
        else:
            diff = ImageChops.difference(img_trans, first_frames[pitch_type])
            diff_arr = np.array(diff)
            threshold = 250
            extreme_points = np.where(np.any(diff_arr[:, :, :3] > threshold, axis=-1))
    
            extreme_points_array = np.array(extreme_points).transpose()
        
            if extreme_points_array.shape[0] > 0:
                n_clusters_adj = min(n_clusters, extreme_points_array.shape[0])
                kmeans = KMeans(n_clusters=n_clusters_adj, n_init=10)
                kmeans.fit(extreme_points_array)
                centers = kmeans.cluster_centers_
    
                highlight_img_arr = np.zeros_like(diff_arr)
                highlight_img_arr[extreme_points] = [255, 0, 0, 255]
                highlight_img_pil = Image.fromarray(highlight_img_arr)
                highlight_composite = Image.alpha_composite(img_trans, highlight_img_pil)
    
                images_per_pitch_type[pitch_type].append((highlight_composite, centers))
    
    for pitch_type, images in images_per_pitch_type.items():
        n_rows = -(-len(images) // 4)
    
        for row in range(n_rows):
            fig, axs = plt.subplots(1, 4, figsize=(20, 5))
            fig.suptitle(f'Pitch type: {pitch_type}, Row: {row + 1}')
    
            for i in range(4):
                index = row * 4 + i
                if index < len(images):
                    highlight_composite, centers = images[index]
                    axs[i].imshow(highlight_composite)
    
                    for center in centers:
                        circle = patches.Circle((center[1], center[0]), circle_radius, edgecolor='blue', facecolor='none', linewidth=2)
                        axs[i].add_patch(circle)
                else:
                    axs[i].axis('off')
    

![](https://storage.googleapis.com/papyrus_images/5ab8041fffd292b6919b69a5ce18f249e3d684915540c0ef5771919e5471c364.jpg)

* * *

各画像に対応するファイル名をタイトルとして追加
-----------------------

このコードは、ピッチタイプごとに複数の画像を表示し、各画像に対応するファイル名をタイトルとして追加するために修正されています。具体的な変更点は以下の通りです：

1.  各画像とその対応するファイル名をタプルとして保存します。これはimages\_per\_pitch\_type\[pitch\_type\].append((file\_name, highlight\_composite, centers))によって行われています。
    
2.  各サブプロット（各画像）のタイトルとしてファイル名を設定します。これはaxs\[i\].set\_title(file\_name)によって行われています。
    

これにより、各画像がどのファイルに対応しているかを簡単に特定することが可能になり、結果の解析がさらに容易になります。

    import os
    from PIL import Image, ImageChops
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.cluster import KMeans
    from matplotlib import patches
    from collections import defaultdict
    
    def transparent_background(image, lower_green, upper_green):
        img = image.copy()
        img = img.convert("RGBA")
        datas = img.getdata()
    
        newData = []
        for item in datas:
            if item[0] in list(range(lower_green[0], upper_green[0])) and item[1] in list(range(lower_green[1], upper_green[1])) and item[2] in list(range(lower_green[2], upper_green[2])):
                newData.append((255, 255, 255, 0))
            else:
                newData.append(item)
    
        img.putdata(newData)
        return img
    
    folder_path = '/content/drive/MyDrive/Colab Notebooks/test/2'
    lower_green = (0, 120, 0)
    upper_green = (100, 255, 100)
    n_clusters = 4
    circle_radius = 20
    file_names = os.listdir(folder_path)
    first_frames = defaultdict(lambda: None)
    images_per_pitch_type = defaultdict(lambda: [])
    
    for file_name in sorted(file_names):
        if not os.path.splitext(file_name)[1] in [".png", ".jpg", ".jpeg"]:
            continue
    
        img_path = os.path.join(folder_path, file_name)
        img = Image.open(img_path).convert('RGBA')
        
        img_trans = transparent_background(img, lower_green, upper_green)
        pitch_type = file_name.split('-')[1].split('.')[0]
    
        if first_frames[pitch_type] is None:
            first_frames[pitch_type] = img_trans
        else:
            diff = ImageChops.difference(img_trans, first_frames[pitch_type])
            diff_arr = np.array(diff)
            threshold = 250
            extreme_points = np.where(np.any(diff_arr[:, :, :3] > threshold, axis=-1))
    
            extreme_points_array = np.array(extreme_points).transpose()
        
            if extreme_points_array.shape[0] > 0:
                n_clusters_adj = min(n_clusters, extreme_points_array.shape[0])
                kmeans = KMeans(n_clusters=n_clusters_adj, n_init=10)
                kmeans.fit(extreme_points_array)
                centers = kmeans.cluster_centers_
    
                highlight_img_arr = np.zeros_like(diff_arr)
                highlight_img_arr[extreme_points] = [255, 0, 0, 255]
                highlight_img_pil = Image.fromarray(highlight_img_arr)
                highlight_composite = Image.alpha_composite(img_trans, highlight_img_pil)
    
                images_per_pitch_type[pitch_type].append((file_name, highlight_composite, centers))
    
    for pitch_type, images in images_per_pitch_type.items():
        n_rows = -(-len(images) // 4)
    
        for row in range(n_rows):
            fig, axs = plt.subplots(1, 4, figsize=(20, 5))
            fig.suptitle(f'Pitch type: {pitch_type}, Row: {row + 1}')
    
            for i in range(4):
                index = row * 4 + i
                if index < len(images):
                    file_name, highlight_composite, centers = images[index]
                    axs[i].imshow(highlight_composite)
                    axs[i].set_title(file_name)
    
                    for center in centers:
                        circle = patches.Circle((center[1], center[0]), circle_radius, edgecolor='blue', facecolor='none', linewidth=2)
                        axs[i].add_patch(circle)
                else:
                    axs[i].axis('off')
    

![](https://storage.googleapis.com/papyrus_images/bdddeb5eadecc8bab97065e3126da0759502902992f751936e4e54750b0d1f86.jpg)

* * *

指定した画像（例えば "18-FF.png"）を基準にして他の全ての画像と比較するようにコードを変更する
----------------------------------------------------

    import os
    from PIL import Image, ImageChops
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.cluster import KMeans
    from matplotlib import patches
    from collections import defaultdict
    
    def transparent_background(image, lower_green, upper_green):
        img = image.copy()
        img = img.convert("RGBA")
        datas = img.getdata()
    
        newData = []
        for item in datas:
            if item[0] in list(range(lower_green[0], upper_green[0])) and item[1] in list(range(lower_green[1], upper_green[1])) and item[2] in list(range(lower_green[2], upper_green[2])):
                newData.append((255, 255, 255, 0))
            else:
                newData.append(item)
    
        img.putdata(newData)
        return img
    
    folder_path = '/content/drive/MyDrive/Colab Notebooks/test/2'
    lower_green = (0, 120, 0)
    upper_green = (100, 255, 100)
    n_clusters = 4
    circle_radius = 20
    file_names = os.listdir(folder_path)
    
    # Open the reference image
    reference_image_name = '18-FF.png'
    reference_image_path = os.path.join(folder_path, reference_image_name)
    reference_img = Image.open(reference_image_path).convert('RGBA')
    reference_img_trans = transparent_background(reference_img, lower_green, upper_green)
    
    images_per_pitch_type = defaultdict(lambda: [])
    
    for file_name in sorted(file_names):
        if not os.path.splitext(file_name)[1] in [".png", ".jpg", ".jpeg"]:
            continue
    
        img_path = os.path.join(folder_path, file_name)
        img = Image.open(img_path).convert('RGBA')
        
        img_trans = transparent_background(img, lower_green, upper_green)
        pitch_type = file_name.split('-')[1].split('.')[0]
    
        # Compute the difference with the reference image
        diff = ImageChops.difference(img_trans, reference_img_trans)
        diff_arr = np.array(diff)
        threshold = 250
        extreme_points = np.where(np.any(diff_arr[:, :, :3] > threshold, axis=-1))
    
        extreme_points_array = np.array(extreme_points).transpose()
    
        if extreme_points_array.shape[0] > 0:
            n_clusters_adj = min(n_clusters, extreme_points_array.shape[0])
            kmeans = KMeans(n_clusters=n_clusters_adj, n_init=10)
            kmeans.fit(extreme_points_array)
            centers = kmeans.cluster_centers_
    
            highlight_img_arr = np.zeros_like(diff_arr)
            highlight_img_arr[extreme_points] = [255, 0, 0, 255]
            highlight_img_pil = Image.fromarray(highlight_img_arr)
            highlight_composite = Image.alpha_composite(img_trans, highlight_img_pil)
    
            images_per_pitch_type[pitch_type].append((file_name, highlight_composite, centers))
    
    for pitch_type, images in images_per_pitch_type.items():
        n_rows = -(-len(images) // 4)
    
        for row in range(n_rows):
            fig, axs = plt.subplots(1, 4, figsize=(20, 5))
            fig.suptitle(f'Pitch type: {pitch_type}, Row: {row + 1}')
    
            for i in range(4):
                index = row * 4 + i
                if index < len(images):
                    file_name, highlight_composite, centers = images[index]
                    axs[i].imshow(highlight_composite)
                    axs[i].set_title(file_name)
    
                    for center in centers:
                        circle = patches.Circle((center[1], center[0]), circle_radius, edgecolor='blue', facecolor='none', linewidth=2)
                        axs[i].add_patch(circle)
                else:
                    axs[i].axis('off')
    

![](https://storage.googleapis.com/papyrus_images/ea9e69935398a90444185c8080b4dbf244dc9b9bc9a4e57c75081795b334c1f2.jpg)

* * *

「FF」球種の内部的な変動（つまり「誤差」）を評価し、その情報を他の球種の画像との比較には反映させない
---------------------------------------------------

ここでの主な目標は、「FF」球種の内部的な変動（つまり「誤差」）を評価し、その情報を他の球種の画像との比較には反映させないということだと理解しました。これを実現するための一つのアプローチは、以下の手順で行います。

1.  「FF」球種の画像の差分を計算します（これは既に行われているようです）。
    
2.  この差分の統計的な評価を行い、何を「誤差」と定義するかを決定します。例えば、差分の平均と標準偏差を計算し、任意の差分が平均±1SD（または2SDなど）以内であれば「誤差」範囲内と判断する、などです。
    
3.  「FF」球種の画像の分析では、この「誤差」範囲を考慮に入れます。例えば、差分が「誤差」範囲内であれば、それを0に設定します。
    
4.  他の球種の画像に対しては、この「誤差」の調整は行わないで分析を行います。
    

    import os
    from PIL import Image, ImageChops
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.cluster import KMeans
    from matplotlib import patches
    from collections import defaultdict
    
    def transparent_background(image, lower_green, upper_green):
        img = image.copy()
        img = img.convert("RGBA")
        datas = img.getdata()
    
        newData = []
        for item in datas:
            if item[0] in list(range(lower_green[0], upper_green[0])) and item[1] in list(range(lower_green[1], upper_green[1])) and item[2] in list(range(lower_green[2], upper_green[2])):
                newData.append((255, 255, 255, 0))
            else:
                newData.append(item)
    
        img.putdata(newData)
        return img
    
    folder_path = '/content/drive/MyDrive/Colab Notebooks/test/2'
    lower_green = (0, 120, 0)
    upper_green = (100, 255, 100)
    n_clusters = 4
    circle_radius = 20
    file_names = os.listdir(folder_path)
    
    # Open the reference image
    reference_image_name = '18-FF.png'
    reference_image_path = os.path.join(folder_path, reference_image_name)
    reference_img = Image.open(reference_image_path).convert('RGBA')
    reference_img_trans = transparent_background(reference_img, lower_green, upper_green)
    
    # Initialize a list to store the difference arrays for FF pitch type
    ff_diffs = []
    
    for file_name in sorted(file_names):
        if not os.path.splitext(file_name)[1] in [".png", ".jpg", ".jpeg"]:
            continue
    
        img_path = os.path.join(folder_path, file_name)
        img = Image.open(img_path).convert('RGBA')
        img_trans = transparent_background(img, lower_green, upper_green)
        pitch_type = file_name.split('-')[1].split('.')[0]
    
        # Compute the difference with the reference image
        diff = ImageChops.difference(img_trans, reference_img_trans)
        diff_arr = np.array(diff)
        
        # Store the difference array for FF pitch type
        if pitch_type == 'FF':
            ff_diffs.append(diff_arr)
    
    # Compute the mean and standard deviation of the differences for FF pitch type
    ff_diffs_arr = np.array(ff_diffs)
    mean_ff_diff = np.mean(ff_diffs_arr)
    std_ff_diff = np.std(ff_diffs_arr)
    
    images_per_pitch_type = defaultdict(lambda: [])
    
    for file_name in sorted(file_names):
        if not os.path.splitext(file_name)[1] in [".png", ".jpg", ".jpeg"]:
            continue
    
        img_path = os.path.join(folder_path, file_name)
        img = Image.open(img_path).convert('RGBA')
        img_trans = transparent_background(img, lower_green, upper_green)
        pitch_type = file_name.split('-')[1].split('.')[0]
    
        # Compute the difference with the reference image
        diff = ImageChops.difference(img_trans, reference_img_trans)
        diff_arr = np.array(diff)
    
        # Adjust the differences for FF pitch type considering the 'error'
        if pitch_type == 'FF':
            diff_arr = np.where((mean_ff_diff - std_ff_diff <= diff_arr) & (diff_arr <= mean_ff_diff + std_ff_diff), 0, diff_arr)
    
        threshold = 250
        extreme_points = np.where(np.any(diff_arr[:, :, :3] > threshold, axis=-1))
        extreme_points_array = np.array(extreme_points).transpose()
    
        if extreme_points_array.shape[0] > 0:
            n_clusters_adj = min(n_clusters, extreme_points_array.shape[0])
            kmeans = KMeans(n_clusters=n_clusters_adj, n_init=10)
            kmeans.fit(extreme_points_array)
            centers = kmeans.cluster_centers_
    
            highlight_img_arr = np.zeros_like(diff_arr)
            highlight_img_arr[extreme_points] = [255, 0, 0, 255]
            highlight_img_pil = Image.fromarray(highlight_img_arr)
            highlight_composite = Image.alpha_composite(img_trans, highlight_img_pil)
    
            images_per_pitch_type[pitch_type].append((file_name, highlight_composite, centers))
    
    for pitch_type, images in images_per_pitch_type.items():
        n_rows = -(-len(images) // 4)
    
        for row in range(n_rows):
            fig, axs = plt.subplots(1, 4, figsize=(20, 5))
            fig.suptitle(f'Pitch type: {pitch_type}, Row: {row + 1}')
    
            for i in range(4):
                index = row * 4 + i
                if index < len(images):
                    file_name, highlight_composite, centers = images[index]
                    axs[i].imshow(highlight_composite)
                    axs[i].set_title(file_name)
    
                    for center in centers:
                        circle = patches.Circle((center[1], center[0]), circle_radius, edgecolor='blue', facecolor='none', linewidth=2)
                        axs[i].add_patch(circle)
                else:
                    axs[i].axis('off')
    

![](https://storage.googleapis.com/papyrus_images/190b658a890d89fd7b3a0fd0558222f7d64fd009a0ff9e64e80fa56be35ddace.jpg)

* * *

1シグマ → 2シグマ
-----------

    import os
    from PIL import Image, ImageChops
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.cluster import KMeans
    from matplotlib import patches
    from collections import defaultdict
    
    def transparent_background(image, lower_green, upper_green):
        img = image.copy()
        img = img.convert("RGBA")
        datas = img.getdata()
    
        newData = []
        for item in datas:
            if item[0] in list(range(lower_green[0], upper_green[0])) and item[1] in list(range(lower_green[1], upper_green[1])) and item[2] in list(range(lower_green[2], upper_green[2])):
                newData.append((255, 255, 255, 0))
            else:
                newData.append(item)
    
        img.putdata(newData)
        return img
    
    folder_path = '/content/drive/MyDrive/Colab Notebooks/test/2'
    lower_green = (0, 120, 0)
    upper_green = (100, 255, 100)
    n_clusters = 4
    circle_radius = 20
    file_names = os.listdir(folder_path)
    
    # Open the reference image
    reference_image_name = '18-FF.png'
    reference_image_path = os.path.join(folder_path, reference_image_name)
    reference_img = Image.open(reference_image_path).convert('RGBA')
    reference_img_trans = transparent_background(reference_img, lower_green, upper_green)
    
    # Initialize a list to store the difference arrays for FF pitch type
    ff_diffs = []
    
    for file_name in sorted(file_names):
        if not os.path.splitext(file_name)[1] in [".png", ".jpg", ".jpeg"]:
            continue
    
        img_path = os.path.join(folder_path, file_name)
        img = Image.open(img_path).convert('RGBA')
        img_trans = transparent_background(img, lower_green, upper_green)
        pitch_type = file_name.split('-')[1].split('.')[0]
    
        # Compute the difference with the reference image
        diff = ImageChops.difference(img_trans, reference_img_trans)
        diff_arr = np.array(diff)
        
        # Store the difference array for FF pitch type
        if pitch_type == 'FF':
            ff_diffs.append(diff_arr)
    
    # Compute the mean and standard deviation of the differences for FF pitch type
    ff_diffs_arr = np.array(ff_diffs)
    mean_ff_diff = np.mean(ff_diffs_arr)
    std_ff_diff = np.std(ff_diffs_arr)
    
    images_per_pitch_type = defaultdict(lambda: [])
    
    for file_name in sorted(file_names):
        if not os.path.splitext(file_name)[1] in [".png", ".jpg", ".jpeg"]:
            continue
    
        img_path = os.path.join(folder_path, file_name)
        img = Image.open(img_path).convert('RGBA')
        img_trans = transparent_background(img, lower_green, upper_green)
        pitch_type = file_name.split('-')[1].split('.')[0]
    
        # Compute the difference with the reference image
        diff = ImageChops.difference(img_trans, reference_img_trans)
        diff_arr = np.array(diff)
    
    
        # Adjust the differences for FF pitch type considering the 'error'
        # Let's say you want to adjust to mean ± 2SD instead of mean ± 1SD
        if pitch_type == 'FF':
          diff_arr = np.where((mean_ff_diff - 2*std_ff_diff <= diff_arr) & (diff_arr <= mean_ff_diff + 2*std_ff_diff), 0, diff_arr)
    
    
        threshold = 250
        extreme_points = np.where(np.any(diff_arr[:, :, :3] > threshold, axis=-1))
        extreme_points_array = np.array(extreme_points).transpose()
    
        if extreme_points_array.shape[0] > 0:
            n_clusters_adj = min(n_clusters, extreme_points_array.shape[0])
            kmeans = KMeans(n_clusters=n_clusters_adj, n_init=10)
            kmeans.fit(extreme_points_array)
            centers = kmeans.cluster_centers_
    
            highlight_img_arr = np.zeros_like(diff_arr)
            highlight_img_arr[extreme_points] = [255, 0, 0, 255]
            highlight_img_pil = Image.fromarray(highlight_img_arr)
            highlight_composite = Image.alpha_composite(img_trans, highlight_img_pil)
    
            images_per_pitch_type[pitch_type].append((file_name, highlight_composite, centers))
    
    for pitch_type, images in images_per_pitch_type.items():
        n_rows = -(-len(images) // 4)
    
        for row in range(n_rows):
            fig, axs = plt.subplots(1, 4, figsize=(20, 5))
            fig.suptitle(f'Pitch type: {pitch_type}, Row: {row + 1}')
    
            for i in range(4):
                index = row * 4 + i
                if index < len(images):
                    file_name, highlight_composite, centers = images[index]
                    axs[i].imshow(highlight_composite)
                    axs[i].set_title(file_name)
    
                    for center in centers:
                        circle = patches.Circle((center[1], center[0]), circle_radius, edgecolor='blue', facecolor='none', linewidth=2)
                        axs[i].add_patch(circle)
                else:
                    axs[i].axis('off')
    

![](https://storage.googleapis.com/papyrus_images/359eeb933fa76a8103d70d36c3364ac16a61b7c692b135a7bd524253cb5f0fcc.jpg)

* * *

けっこう面白いかもね

---

*Originally published on [Shogaku](https://paragraph.com/@shogaku/python)*
