1. ホーム
  2. python

[解決済み】肉球の検出力を高めるにはどうしたらいいですか?

2022-04-15 20:18:10

質問

前回の質問で 前足の指を探す と思い、他の測定値を読み込んでみました。しかし、残念ながら、その前のステップの一つである前足の認識ですぐに問題にぶち当たりました。

私の概念実証では、基本的に各センサーの最大圧力を時間経過とともに取得し、各行の合計を探し始め、「0.0」を見つけるまででした。そして、列に対しても同じことを行い、2行以上0を見つけるとすぐにまた0にします。行と列の値の最小値と最大値をインデックスに格納します。

図にあるように、これはほとんどの場合において非常にうまく機能します。しかし、この方法には多くの欠点があります(非常に原始的であること以外に)。

  • 人間は「足がくぼむ」ことがあり、足跡の中に何列も空洞ができる。大型犬でも同じことが起こる可能性があるので、少なくとも2~3列は空くのを待ってから肉球を切り落としました。

    このため、いくつかの空いた列に到達する前に別の列で接触してしまい、領域が広がってしまうという問題が発生します。列を比較して、ある値を超えたら、別の前足であることを確認すればいいと考えています。

  • この問題は、犬がとても小さかったり、歩く速度が速かったりすると、さらに悪化します。前足のつま先はまだ接触しているのに、後足のつま先は前足と同じ範囲で接触し始めるのです。

    私の簡単なスクリプトでは、この2つを分割することはできません。なぜなら、その領域のどのフレームがどの肉球に属するかを判断しなければならないからで、現在はすべてのフレームの最大値を見ればよいのです。

どこでおかしくなり始めるかの例。

そこで今、前脚を認識し分離するためのより良い方法を探しています。 (この後、どの前足か判断する問題に入ります!)。

更新しました。

Joeの回答(素晴らしい!)を実装するためにいじっているのですが、私のファイルから実際の肉球データを抽出するのに苦労しています。

coded_pawsは、最大圧力画像(上記参照)に適用すると、すべての異なる前足を表示してくれます。しかし、このソリューションでは、各フレームを確認し(重なり合う前足を分離するため)、座標や高さ/幅など、4つのRectangle属性を設定します。

これらの属性を取り出し、何らかの変数に格納して、測定データに適用する方法がわかりません。各足について、どのフレームでどの位置にあるのかを知る必要があり、これをどの足であるか(前/後、左/右)と関連付ける必要があるからです。

では、Rectangles 属性を使用して、各手足の値を抽出するにはどうすればよいのでしょうか。

質問で使用した測定値は、私の公開Dropboxフォルダにセットアップしてあります( 例1 , 例2 , 例3 ). 興味のある方のために、ブログも開設してみました 最新情報をお届けします :-)

解決するには?

もし、単に(半)連続した領域が欲しいだけなら、Pythonで簡単な実装が既にあります。 SciPy 's ndimage.morphology モジュールを使用します。 これはかなり一般的な イメージモーフォロジー の操作になります。


基本的には5ステップです。

def find_paws(data, smooth_radius=5, threshold=0.0001):
    data = sp.ndimage.uniform_filter(data, smooth_radius)
    thresh = data > threshold
    filled = sp.ndimage.morphology.binary_fill_holes(thresh)
    coded_paws, num_paws = sp.ndimage.label(filled)
    data_slices = sp.ndimage.find_objects(coded_paws)
    return object_slices

  1. 入力データを少しぼかして、前足が連続した足跡になるようにする。 (単に大きなカーネルを使う方が効率的です( structure kwarg を様々な scipy.ndimage.morphology という関数がありますが、これはなぜかうまく動作しません...)

  2. 圧力がある閾値以上である場所のブール配列ができるように、配列を閾値化します (すなわち thresh = data > value )

  3. 内部の穴を埋めて、よりきれいな領域を作る ( filled = sp.ndimage.morphology.binary_fill_holes(thresh) )

  4. 分離した連続した領域を探す( coded_paws, num_paws = sp.ndimage.label(filled) ). これは、領域を番号でコード化した配列を返します(各領域は、一意の整数(1 から前足の数まで)の連続した領域で、それ以外はすべてゼロです))。

  5. を使用して、連続した領域を分離します。 data_slices = sp.ndimage.find_objects(coded_paws) . のタプルのリストが返されます。 slice オブジェクトを使用することで、各足に対応するデータの領域を [data[x] for x in data_slices] . その代わり、これらのスライスを元に矩形を描きますが、これには少し手間がかかります。


以下の2つのアニメーションは、あなたの"Overlapping Paws"と"Grouped Paws"のサンプルデータを示しています。 この方法は完璧に動作しているようです。(参考までに、私のマシンでは下の GIF 画像よりもずっとスムーズに動作しているので、肉球検出アルゴリズムはかなり高速だと思われます...)


ここに全例があります(現在はもっと詳しい説明があります)。この例の大部分は、入力を読み取り、アニメーションを作成しています。 実際の手足の検出は、わずか5行のコードです。

import numpy as np
import scipy as sp
import scipy.ndimage

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle

def animate(input_filename):
    """Detects paws and animates the position and raw data of each frame
    in the input file"""
    # With matplotlib, it's much, much faster to just update the properties
    # of a display object than it is to create a new one, so we'll just update
    # the data and position of the same objects throughout this animation...

    infile = paw_file(input_filename)

    # Since we're making an animation with matplotlib, we need 
    # ion() instead of show()...
    plt.ion()
    fig = plt.figure()
    ax = fig.add_subplot(111)
    fig.suptitle(input_filename)

    # Make an image based on the first frame that we'll update later
    # (The first frame is never actually displayed)
    im = ax.imshow(infile.next()[1])

    # Make 4 rectangles that we can later move to the position of each paw
    rects = [Rectangle((0,0), 1,1, fc='none', ec='red') for i in range(4)]
    [ax.add_patch(rect) for rect in rects]

    title = ax.set_title('Time 0.0 ms')

    # Process and display each frame
    for time, frame in infile:
        paw_slices = find_paws(frame)

        # Hide any rectangles that might be visible
        [rect.set_visible(False) for rect in rects]

        # Set the position and size of a rectangle for each paw and display it
        for slice, rect in zip(paw_slices, rects):
            dy, dx = slice
            rect.set_xy((dx.start, dy.start))
            rect.set_width(dx.stop - dx.start + 1)
            rect.set_height(dy.stop - dy.start + 1)
            rect.set_visible(True)

        # Update the image data and title of the plot
        title.set_text('Time %0.2f ms' % time)
        im.set_data(frame)
        im.set_clim([frame.min(), frame.max()])
        fig.canvas.draw()

def find_paws(data, smooth_radius=5, threshold=0.0001):
    """Detects and isolates contiguous regions in the input array"""
    # Blur the input data a bit so the paws have a continous footprint 
    data = sp.ndimage.uniform_filter(data, smooth_radius)
    # Threshold the blurred data (this needs to be a bit > 0 due to the blur)
    thresh = data > threshold
    # Fill any interior holes in the paws to get cleaner regions...
    filled = sp.ndimage.morphology.binary_fill_holes(thresh)
    # Label each contiguous paw
    coded_paws, num_paws = sp.ndimage.label(filled)
    # Isolate the extent of each paw
    data_slices = sp.ndimage.find_objects(coded_paws)
    return data_slices

def paw_file(filename):
    """Returns a iterator that yields the time and data in each frame
    The infile is an ascii file of timesteps formatted similar to this:

    Frame 0 (0.00 ms)
    0.0 0.0 0.0
    0.0 0.0 0.0

    Frame 1 (0.53 ms)
    0.0 0.0 0.0
    0.0 0.0 0.0
    ...
    """
    with open(filename) as infile:
        while True:
            try:
                time, data = read_frame(infile)
                yield time, data
            except StopIteration:
                break

def read_frame(infile):
    """Reads a frame from the infile."""
    frame_header = infile.next().strip().split()
    time = float(frame_header[-2][1:])
    data = []
    while True:
        line = infile.next().strip().split()
        if line == []:
            break
        data.append(line)
    return time, np.array(data, dtype=np.float)

if __name__ == '__main__':
    animate('Overlapping paws.bin')
    animate('Grouped up paws.bin')
    animate('Normal measurement.bin')


更新しました。 どの足がどの時間にセンサーに接触しているかを特定する限り、最も簡単な解決策は、同じ分析を行うだけで、すべてのデータを一度に使用することです。(つまり、入力を3D配列にスタックして、個々の時間枠の代わりにそれを扱う)。 SciPyのndimage関数はn次元配列で動作するように設計されているので、元の肉球を見つける関数を変更する必要は全くないのです。

# This uses functions (and imports) in the previous code example!!
def paw_regions(infile):
    # Read in and stack all data together into a 3D array
    data, time = [], []
    for t, frame in paw_file(infile):
        time.append(t)
        data.append(frame)
    data = np.dstack(data)
    time = np.asarray(time)

    # Find and label the paw impacts
    data_slices, coded_paws = find_paws(data, smooth_radius=4)

    # Sort by time of initial paw impact... This way we can determine which
    # paws are which relative to the first paw with a simple modulo 4.
    # (Assuming a 4-legged dog, where all 4 paws contacted the sensor)
    data_slices.sort(key=lambda dat_slice: dat_slice[2].start)

    # Plot up a simple analysis
    fig = plt.figure()
    ax1 = fig.add_subplot(2,1,1)
    annotate_paw_prints(time, data, data_slices, ax=ax1)
    ax2 = fig.add_subplot(2,1,2)
    plot_paw_impacts(time, data_slices, ax=ax2)
    fig.suptitle(infile)

def plot_paw_impacts(time, data_slices, ax=None):
    if ax is None:
        ax = plt.gca()

    # Group impacts by paw...
    for i, dat_slice in enumerate(data_slices):
        dx, dy, dt = dat_slice
        paw = i%4 + 1
        # Draw a bar over the time interval where each paw is in contact
        ax.barh(bottom=paw, width=time[dt].ptp(), height=0.2, 
                left=time[dt].min(), align='center', color='red')
    ax.set_yticks(range(1, 5))
    ax.set_yticklabels(['Paw 1', 'Paw 2', 'Paw 3', 'Paw 4'])
    ax.set_xlabel('Time (ms) Since Beginning of Experiment')
    ax.yaxis.grid(True)
    ax.set_title('Periods of Paw Contact')

def annotate_paw_prints(time, data, data_slices, ax=None):
    if ax is None:
        ax = plt.gca()

    # Display all paw impacts (sum over time)
    ax.imshow(data.sum(axis=2).T)

    # Annotate each impact with which paw it is
    # (Relative to the first paw to hit the sensor)
    x, y = [], []
    for i, region in enumerate(data_slices):
        dx, dy, dz = region
        # Get x,y center of slice...
        x0 = 0.5 * (dx.start + dx.stop)
        y0 = 0.5 * (dy.start + dy.stop)
        x.append(x0); y.append(y0)

        # Annotate the paw impacts         
        ax.annotate('Paw %i' % (i%4 +1), (x0, y0),  
            color='red', ha='center', va='bottom')

    # Plot line connecting paw impacts
    ax.plot(x,y, '-wo')
    ax.axis('image')
    ax.set_title('Order of Steps')

<イグ