pandasとの連携 -- 2値ブローブ特徴量の一覧表示とフィルタリング


pandas を用いて2値ブローブ特徴量の一覧表示とフィルタリングを行うサンプルコードです。 本サンプルではブローブ特徴量をCSV形式で保存し、独自に定義したブローブ特徴量の楕円度が上位3位以内の黒ブローブをプロットします。

本サンプルの実行には予め pandas をインストールしておく必要があります。 pip コマンドを使用する場合、次のようにインストールしてください。

pip install pandas==1.0.5

コード

import math

import pyfie
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# 利便のため PyFIE 関数が返すエラーコードに応じて例外を発生させる機能を有効化
pyfie.ctrl.enable_f_err_exception(True)

def main():
    # 入力画像読み込み
    hbin = pyfie.imread("fast_bin.png")

    # 二値ブローブ解析
    params = pyfie.F_MEASURE_PARAMS(
        max_runs = 0,
        max_blobs = 0,
        max_rows = 0,
        color_mode = pyfie.f_measure_color_mode.F_MEASURE_WHITEFG_BLACKBG,
        neighborhood = 8,
        precalc_features = 0,
        keep_results_after_overflow = False
    )
    measure = pyfie.fnFIE_measure_execute(
        hbin, (0, 0), params, None
    )
    filter_num = 1
    filters = pyfie.F_MEASURE_FILTER_RANGE.ARRAY(filter_num)
    filters.value = [
        (pyfie.F_FEATURE_AREA, 10, 100000000)
    ]
    # ブローブ番号配列の取得
    blob_numbers = pyfie.UINT.PTR()
    blob_num = pyfie.UINT()
    pyfie.fnFIE_measure_get_list(
        measure, filters, filter_num, blob_numbers.ref, blob_num.ref
    )

    # 検出されたブローブをプロットしてファイルに保存
    hbin.imshow()
    pyfie.mpltools.plot_measure_results(measure, blob_numbers, blob_num)
    plt.savefig("blobs.png")
    plt.close()

    # テーブル化したいブローブ特徴量
    features = [
        pyfie.F_FEATURE_COLOR,
        pyfie.F_FEATURE_CENTERX,
        pyfie.F_FEATURE_CENTERY,
        pyfie.F_FEATURE_XDIFF,
        pyfie.F_FEATURE_YDIFF,
        pyfie.F_FEATURE_AREA,
        pyfie.F_FEATURE_MAJORAXIS,
        pyfie.F_FEATURE_MINORAXIS,
        pyfie.F_FEATURE_CONVEX_AREA,
        pyfie.F_FEATURE_PERIM,
    ]

    # ブローブ特徴量のテーブルを作成
    feature_table = get_feature_table(measure, blob_numbers, blob_num, features)

    # pandas データフレームに変換
    df = pd.DataFrame(feature_table, index=blob_numbers[:blob_num], dtype=np.float)

    # 「楕円度」を表す独自のブローブ特徴量を追加。
    # 楕円度はおおむね0から1の値を取り、1に近いほど楕円らしいことを示す
    df["ellipticity"] = df["area"] / (df["majoraxis"] * df["minoraxis"] * math.pi)

    # 楕円度を含むブローブ特徴量の一覧をCSVファイルに保存
    df.to_csv("blob_features.csv")

    # 色が黒 (0) であり、楕円度が上位3位以内のブローブ番号を取得
    NUM = 3
    most_elliptic_blobnos = df[df["color"] == 0]["ellipticity"].nlargest(NUM).index

    # 上記ブローブのバウンディングボックスをプロットしてファイルに保存
    hbin.imshow()
    pyfie.mpltools.plot_measure_results(measure, most_elliptic_blobnos, len(most_elliptic_blobnos))
    plt.savefig("elliptic_blobs.png")

# ----- 二値ブローブ特徴量取得のためのユーティリティ関数 ここから -----

def get_feature_table(hmeasure, blob_numbers, nblobs, features):
    """Pandas互換のブローブ特徴量のテーブルを作成する。"""
    feature_table = {}
    for feature_id in features:
        feature_vals = []
        for i in range(nblobs):
            blobno = blob_numbers[i]
            feature_val = _get_blob_prop(hmeasure, blobno, feature_id)
            feature_vals.append(feature_val)
        feature_name = _get_abbreviated_measure_feature_name(feature_id)
        feature_table[feature_name] = feature_vals
    return feature_table

def _get_blob_prop(hmeasure, blobno, feature_type):
    def get_1(func, value_type):
        v = value_type()
        err = func(hmeasure, blobno, v)
        if err != pyfie.F_ERR_NONE:
            raise RuntimeError("faild to get a blob property")
        return v
    def get_d(func):
        return get_1(func, pyfie.DOUBLE)
    def get_ui(func):
        return get_1(func, pyfie.UINT)

    def get_n(func, value_types, idx):
        vs = [value_type() for value_type in value_types]
        err = func(hmeasure, blobno, *vs)
        if err != pyfie.F_ERR_NONE:
            raise RuntimeError("faild to get blob properties")
        return vs[idx]
    def get_i2(func, idx):
        return get_n(func, [pyfie.INT] * 2, idx)
    def get_i4(func, idx):
        return get_n(func, [pyfie.INT] * 4, idx)
    def get_ll2(func, idx):
        return get_n(func, [pyfie.DLONG] * 2, idx)
    def get_d2(func, idx):
        return get_n(func, [pyfie.DOUBLE] * 2, idx)
    def get_d3(func, idx):
        return get_n(func, [pyfie.DOUBLE] * 3, idx)
    def get_d4(func, idx):
        return get_n(func, [pyfie.DOUBLE] * 4, idx)

    def get_hu_moment(idx):
        hu = pyfie.DOUBLE.ARRAY(7)
        pyfie.fnFIE_measure_get_hu_moments(hmeasure, blobno, hu)
        return hu[idx]

    if feature_type == pyfie.F_FEATURE_COLOR:
        return get_ui(pyfie.fnFIE_measure_get_color)
    elif feature_type == pyfie.F_FEATURE_XMIN:
        return get_i4(pyfie.fnFIE_measure_get_xyrange, 0)
    elif feature_type == pyfie.F_FEATURE_YMIN:
        return get_i4(pyfie.fnFIE_measure_get_xyrange, 1)
    elif feature_type == pyfie.F_FEATURE_XMAX:
        return get_i4(pyfie.fnFIE_measure_get_xyrange, 2)
    elif feature_type == pyfie.F_FEATURE_YMAX:
        return get_i4(pyfie.fnFIE_measure_get_xyrange, 3)
    elif feature_type == pyfie.F_FEATURE_XMIN_AT_YMIN:
        return get_i4(pyfie.fnFIE_measure_get_maxminpos, 0)
    elif feature_type == pyfie.F_FEATURE_XMAX_AT_YMAX:
        return get_i4(pyfie.fnFIE_measure_get_maxminpos, 1)
    elif feature_type == pyfie.F_FEATURE_YMIN_AT_XMAX:
        return get_i4(pyfie.fnFIE_measure_get_maxminpos, 2)
    elif feature_type == pyfie.F_FEATURE_YMAX_AT_XMIN:
        return get_i4(pyfie.fnFIE_measure_get_maxminpos, 3)
    elif feature_type == pyfie.F_FEATURE_XDIFF:
        return get_i2(pyfie.fnFIE_measure_get_xydiff, 0)
    elif feature_type == pyfie.F_FEATURE_YDIFF:
        return get_i2(pyfie.fnFIE_measure_get_xydiff, 1)
    elif feature_type == pyfie.F_FEATURE_M10:
        assert feature_type == pyfie.F_FEATURE_SUMX
        return get_ll2(pyfie.fnFIE_measure_get_moment1, 0)
    elif feature_type == pyfie.F_FEATURE_M01:
        assert feature_type == pyfie.F_FEATURE_SUMY
        return get_ll2(pyfie.fnFIE_measure_get_moment1, 1)
    elif feature_type == pyfie.F_FEATURE_M20:
        assert feature_type == pyfie.F_FEATURE_SUMX2
        return get_n(pyfie.fnFIE_measure_get_moment2, [pyfie.UDLONG, pyfie.UDLONG, pyfie.DLONG], 0)
    elif feature_type == pyfie.F_FEATURE_M02:
        assert feature_type == pyfie.F_FEATURE_SUMY2
        return get_n(pyfie.fnFIE_measure_get_moment2, [pyfie.UDLONG, pyfie.UDLONG, pyfie.DLONG], 1)
    elif feature_type == pyfie.F_FEATURE_M11:
        assert feature_type == pyfie.F_FEATURE_SUMXY
        return get_n(pyfie.fnFIE_measure_get_moment2, [pyfie.UDLONG, pyfie.UDLONG, pyfie.DLONG], 2)
    elif feature_type == pyfie.F_FEATURE_AREA:
        return get_ui(pyfie.fnFIE_measure_get_area)
    elif feature_type == pyfie.F_FEATURE_CENTERX:
        return get_d2(pyfie.fnFIE_measure_get_center, 0)
    elif feature_type == pyfie.F_FEATURE_CENTERY:
        return get_d2(pyfie.fnFIE_measure_get_center, 1)
    elif feature_type == pyfie.F_FEATURE_RECT1AREA:
        return get_ui(pyfie.fnFIE_measure_get_rect1_area)
    elif feature_type == pyfie.F_FEATURE_RECT1LRATIO:
        return get_d(pyfie.fnFIE_measure_get_rect1_lratio)
    elif feature_type == pyfie.F_FEATURE_RECT1SRATIO:
        return get_d(pyfie.fnFIE_measure_get_rect1_sratio)
    elif feature_type == pyfie.F_FEATURE_LSIZE:
        return get_d2(pyfie.fnFIE_measure_get_rect2_size, 0)
    elif feature_type == pyfie.F_FEATURE_WSIZE:
        return get_d2(pyfie.fnFIE_measure_get_rect2_size, 1)
    elif feature_type == pyfie.F_FEATURE_RECT2AREA:
        return get_d(pyfie.fnFIE_measure_get_rect2_area)
    elif feature_type == pyfie.F_FEATURE_RECT2LRATIO:
        return get_d(pyfie.fnFIE_measure_get_rect2_lratio)
    elif feature_type == pyfie.F_FEATURE_RECT2SRATIO:
        return get_d(pyfie.fnFIE_measure_get_rect2_sratio)
    elif feature_type == pyfie.F_FEATURE_MAJORAXIS:
        return get_d4(pyfie.fnFIE_measure_get_equivalent_ellipse, 0)
    elif feature_type == pyfie.F_FEATURE_MINORAXIS:
        return get_d4(pyfie.fnFIE_measure_get_equivalent_ellipse, 1)
    elif feature_type in (pyfie.F_FEATURE_AXISTHETA, pyfie.F_FEATURE_AXISTHETA_CYCLIC):
        return get_d4(pyfie.fnFIE_measure_get_equivalent_ellipse, 2)
    elif feature_type == pyfie.F_FEATURE_AXISRATIO:
        return get_d4(pyfie.fnFIE_measure_get_equivalent_ellipse, 3)
    elif feature_type == pyfie.F_FEATURE_DIAMETER_EQUIDISK:
        return get_d(pyfie.fnFIE_measure_get_equivalent_disk)
    elif feature_type == pyfie.F_FEATURE_DIAMETER_EQUICIRCLE:
        return get_d(pyfie.fnFIE_measure_get_equivalent_circle)
    elif feature_type == pyfie.F_FEATURE_CIRCULARITY1:
        return get_d(pyfie.fnFIE_measure_get_circularity1)
    elif feature_type == pyfie.F_FEATURE_CIRCULARITY2:
        return get_d(pyfie.fnFIE_measure_get_circularity2)
    elif feature_type == pyfie.F_FEATURE_CIRCULARITY3:
        return get_d(pyfie.fnFIE_measure_get_circularity3)
    elif feature_type == pyfie.F_FEATURE_CONVEX_AREA:
        return get_d2(pyfie.fnFIE_measure_get_convexfeature, 0)
    elif feature_type == pyfie.F_FEATURE_CONVEX_PERIM:
        return get_d2(pyfie.fnFIE_measure_get_convexfeature, 1)
    elif feature_type == pyfie.F_FEATURE_CONVEX_AREARATIO:
        return get_d2(pyfie.fnFIE_measure_get_convexratio, 0)
    elif feature_type == pyfie.F_FEATURE_CONVEX_PERIMRATIO:
        return get_d2(pyfie.fnFIE_measure_get_convexratio, 1)
    elif feature_type == pyfie.F_FEATURE_FERET_MAX:
        return get_d4(pyfie.fnFIE_measure_get_feret_diameter_maxmin, 0)
    elif feature_type == pyfie.F_FEATURE_FERET_MIN:
        return get_d4(pyfie.fnFIE_measure_get_feret_diameter_maxmin, 2)
    elif feature_type in (pyfie.F_FEATURE_FMAX_THETA, pyfie.F_FEATURE_FMAX_THETA_CYCLIC):
        return get_d4(pyfie.fnFIE_measure_get_feret_diameter_maxmin, 1)
    elif feature_type in (pyfie.F_FEATURE_FMIN_THETA, pyfie.F_FEATURE_FMIN_THETA_CYCLIC):
        return get_d4(pyfie.fnFIE_measure_get_feret_diameter_maxmin, 3)
    elif feature_type == pyfie.F_FEATURE_DPMIN:
        return get_d4(pyfie.fnFIE_measure_get_distance_to_boundary, 1)
    elif feature_type == pyfie.F_FEATURE_DPMAX:
        return get_d4(pyfie.fnFIE_measure_get_distance_to_boundary, 0)
    elif feature_type == pyfie.F_FEATURE_DPAVE:
        return get_d4(pyfie.fnFIE_measure_get_distance_to_boundary, 2)
    elif feature_type == pyfie.F_FEATURE_DPSIGMA:
        return get_d4(pyfie.fnFIE_measure_get_distance_to_boundary, 3)
    elif feature_type == pyfie.F_FEATURE_DCMAX:
        return get_d3(pyfie.fnFIE_measure_get_distance_to_childs, 0)
    elif feature_type == pyfie.F_FEATURE_DCMIN:
        return get_d3(pyfie.fnFIE_measure_get_distance_to_childs, 1)
    elif feature_type == pyfie.F_FEATURE_DCAVE:
        return get_d3(pyfie.fnFIE_measure_get_distance_to_childs, 2)
    elif feature_type == pyfie.F_FEATURE_DSMAX:
        return get_d3(pyfie.fnFIE_measure_get_distance_to_siblings, 0)
    elif feature_type == pyfie.F_FEATURE_DSMIN:
        return get_d3(pyfie.fnFIE_measure_get_distance_to_siblings, 1)
    elif feature_type == pyfie.F_FEATURE_DSAVE:
        return get_d3(pyfie.fnFIE_measure_get_distance_to_siblings, 2)
    elif feature_type == pyfie.F_FEATURE_NS:
        return get_ui(pyfie.fnFIE_measure_get_sibling_num)
    elif feature_type == pyfie.F_FEATURE_PERIM:
        return get_d(pyfie.fnFIE_measure_get_perimeter)
    elif feature_type == pyfie.F_FEATURE_ST:
        return get_ui(pyfie.fnFIE_measure_get_area_with_hole)
    elif feature_type == pyfie.F_FEATURE_SC:
        return get_ui(pyfie.fnFIE_measure_get_hole_area)
    elif feature_type == pyfie.F_FEATURE_HOLES:
        return get_ui(pyfie.fnFIE_measure_get_hole_num)
    elif feature_type == pyfie.F_FEATURE_HRATIO:
        return get_d(pyfie.fnFIE_measure_get_hole_ratio)
    elif feature_type == pyfie.F_FEATURE_PPS:
        return get_d(pyfie.fnFIE_measure_get_pps)
    elif feature_type == pyfie.F_FEATURE_M30:
        return get_d4(pyfie.fnFIE_measure_get_moment3, 0)
    elif feature_type == pyfie.F_FEATURE_M03:
        return get_d4(pyfie.fnFIE_measure_get_moment3, 1)
    elif feature_type == pyfie.F_FEATURE_M21:
        return get_d4(pyfie.fnFIE_measure_get_moment3, 2)
    elif feature_type == pyfie.F_FEATURE_M12:
        return get_d4(pyfie.fnFIE_measure_get_moment3, 3)
    elif feature_type == pyfie.F_FEATURE_MG20:
        return get_d3(pyfie.fnFIE_measure_get_central_moment2, 0)
    elif feature_type == pyfie.F_FEATURE_MG02:
        return get_d3(pyfie.fnFIE_measure_get_central_moment2, 1)
    elif feature_type == pyfie.F_FEATURE_MG11:
        return get_d3(pyfie.fnFIE_measure_get_central_moment2, 2)
    elif feature_type == pyfie.F_FEATURE_MG30:
        return get_d4(pyfie.fnFIE_measure_get_central_moment3, 0)
    elif feature_type == pyfie.F_FEATURE_MG03:
        return get_d4(pyfie.fnFIE_measure_get_central_moment3, 1)
    elif feature_type == pyfie.F_FEATURE_MG21:
        return get_d4(pyfie.fnFIE_measure_get_central_moment3, 2)
    elif feature_type == pyfie.F_FEATURE_MG12:
        return get_d4(pyfie.fnFIE_measure_get_central_moment3, 3)
    elif feature_type == pyfie.F_FEATURE_HU_MOMENT0:
        return get_hu_moment(0)
    elif feature_type == pyfie.F_FEATURE_HU_MOMENT1:
        return get_hu_moment(1)
    elif feature_type == pyfie.F_FEATURE_HU_MOMENT2:
        return get_hu_moment(2)
    elif feature_type == pyfie.F_FEATURE_HU_MOMENT3:
        return get_hu_moment(3)
    elif feature_type == pyfie.F_FEATURE_HU_MOMENT4:
        return get_hu_moment(4)
    elif feature_type == pyfie.F_FEATURE_HU_MOMENT5:
        return get_hu_moment(5)
    elif feature_type == pyfie.F_FEATURE_HU_MOMENT6:
        return get_hu_moment(6)
    else:
        raise ValueError("Unknown feature type: " + feature_type)

def _get_abbreviated_measure_feature_name(feature_id):
    feature_name = str(pyfie.f_measure_feature_type(feature_id))
    prefix = "F_FEATURE_"
    if feature_name.startswith(prefix):
        feature_name = feature_name[len(prefix):]
    return feature_name.lower()

# ----- 二値ブローブ特徴量取得のためのユーティリティ関数 ここまで -----

if __name__ == "__main__":
    main()

処理結果例

入力画像

../../_images/fast_bin.png

ブローブ特徴量の一覧

color

centerx

centery

xdiff

ydiff

area

majoraxis

minoraxis

convex_area

perim

ellipticity

1

1.00

76.81

72.54

150.00

150.00

17056.00

94.89

91.30

22201.00

596.00

0.63

2

1.00

72.07

120.57

6.00

8.00

28.00

4.14

2.53

20.50

21.07

0.85

3

0.00

61.20

66.76

103.00

120.00

4006.00

68.51

23.07

5112.00

576.00

0.81

4

0.00

71.30

121.55

25.00

29.00

381.00

16.06

11.47

449.50

116.00

0.66

5

0.00

96.85

120.41

28.00

30.00

445.00

18.35

13.07

629.50

188.00

0.59

6

0.00

122.48

115.90

25.00

29.00

273.00

18.04

9.45

432.00

114.00

0.51

7

0.00

49.11

117.83

26.00

28.00

311.00

17.89

9.03

429.00

128.00

0.61

ブローブ画像

../../_images/blobs.png

楕円度(独自定義)が上位3位以内の黒ブローブの画像

../../_images/elliptic_blobs.png

ダウンロード