足の検出を改善するにはどうすればよいですか?


198

各足の中で足の指見つけることについての私の前の質問の後、私はそれがどのように保持されるかを確認するために他の測定値をロードし始めました。残念ながら、私は前のステップの1つである足の認識に関する問題にすぐに遭遇しました。

ご覧のとおり、私の概念実証では基本的に各センサーの最大圧力を経時的に測定し、!= 0.0が見つかるまで各行の合計を探し始めました。次に、列に対して同じことを行い、それが再びゼロである2つを超える行を見つけるとすぐに。最小および最大の行と列の値をいくつかのインデックスに格納します。

代替テキスト

図からわかるように、これはほとんどの場合非常にうまく機能します。ただし、このアプローチには多くの欠点があります(非常に原始的でないことを除く)。

  • 人間は「中空の足」を持つことができます。これは、足跡自体の中にいくつかの空の行があることを意味します。これが(大型の)犬でも発生するのではないかと恐れていたので、足を切り落とす前に、少なくとも2〜3列の空の列を待っていました。

    これにより、いくつかの空の行に到達する前に別の列で別の連絡先が作成されると問題が発生し、エリアが拡大します。列を比較して、それらが特定の値を超えるかどうかを確認できると思います。それらは別々の足でなければなりません。

  • 犬が非常に小さいか、より速いペースで歩く場合、問題はさらに悪化します。後足のつま先が前足と同じ領域内で接触し始めているのに、前足のつま先がまだ接触していることが起こります!

    私の簡単なスクリプトでは、これらの2つを分割することはできません。これは、その領域のどのフレームがどの足に属しているかを判別する必要があるためですが、現在はすべてのフレームの最大値のみを確認する必要があります。

うまくいかない例:

代替テキスト 代替テキスト

だから今私は足を認識して分離するためのより良い方法を探しています(その後、私はそれがどの足であるかを決定する問題に行きます!)。

更新:

私はジョーの(素晴らしい!)答えを実装するためにいじくり回してきましたが、自分のファイルから実際の足のデータを抽出するのが困難です。

代替テキスト

coded_pa​​wsは、最大圧力画像(上記を参照)に適用すると、さまざまな足をすべて表示します。ただし、ソリューションは各フレームを調べ(重なり合う足を分離するため)、座標や高さ/幅などの4つの長方形属性を設定します。

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

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

質問の設定で使用した測定値をパブリックDropboxフォルダー(例1例2例3)に持っています。興味がある人のために、私はあなたを最新に保つためにブログもセットアップしました :-)


有用な情報を制限している行/列アルゴリズムから離れなければならないようです。
Tamara Wijsman、2010年

12
うわー!猫制御ソフトウェア?
alxx

それは実際には@alxxの犬のデータです;-)しかし、そうです、それはそれらを診断するために使用されます!
Ivo Flipse 2010年

4
どうして?(気にしないでください、知らない方が楽しいです...)
Ben Regenspan

回答:


358

(半)連続した領域だけが必要な場合は、Pythonでの簡単な実装が既にあります:SciPyndimage.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さまざまなscipy.ndimage.morphology関数へのkwarg)を使用する方が効率的ですが、これは何らかの理由で正しく機能していません...)

  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つのアニメーションは、「重複する足」と「グループ化された足」のサンプルデータを示しています。この方法は完全に機能しているようです。(そして、それが価値のあるものであれば、これは私のマシンで下にある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')

代替テキスト


代替テキスト


代替テキスト


82
あなたの答えがどれほど素晴らしいかを説明することすらできません!
Ivo

1
@Ivo:はい、私はJoeにもう少し投票することを楽しみます:)しかし、新しい質問を開始する必要があります。stackoverflow.com/questions/2546780/...
unutbu

2
私は実際にちょうどのを.pngを出しダンプ、としましたconvert *.png output.gif。この例では問題なく動作しましたが、前にimagemagickを使用してマシンをひざまずかせたことは確かです。以前は、このスクリプトsvn.effbot.python-hosting.com/pil/Scripts/gifmaker.pyを使用して、個々のフレームを保存せずに、PythonからアニメーションGIFを直接書きました。お役に立てば幸いです。@unutbuについての質問に例を投稿します。
Joe Kington、2010年

1
情報をありがとう、@ Joe。私の問題の一部はでの使用bbox_inches='tight'を怠っていたことでありplt.savefig、もう1つは焦りでした:)
unutbu

4
聖なる牛、私はこの答えがどれほど素晴らしいかすごいことを言わなければなりません。
andersoj 2010年

4

私は画像検出の専門家ではありませんし、Pythonも知りませんが、それを手に入れるつもりです...

個々の足を検出するには、最初に、圧力が小さなしきい値よりも大きく、圧力がまったくない状態に非常に近いすべてのもののみを選択する必要があります。これより上にあるすべてのピクセル/ポイントは「マーク」されている必要があります。次に、すべての「マークされた」ピクセルに隣接するすべてのピクセルがマークされ、このプロセスが数回繰り返されます。完全に接続された質量が形成されるため、個別のオブジェクトがあります。次に、各「オブジェクト」には最小値と最大値のxとyがあるため、境界ボックスをそれらの周りにきちんとパックできます。

疑似コード:

(MARK) ALL PIXELS ABOVE (0.5)

(MARK) ALL PIXELS (ADJACENT) TO (MARK) PIXELS

REPEAT (STEP 2) (5) TIMES

SEPARATE EACH TOTALLY CONNECTED MASS INTO A SINGLE OBJECT

MARK THE EDGES OF EACH OBJECT, AND CUT APART TO FORM SLICES.

これで問題は解決します。


0

注:ピクセルと言いますが、これはピクセルの平均を使用する領域である可能性があります。最適化は別の問題です...

各ピクセルの関数(時間に対する圧力)を分析し、関数がどこで回転するかを決定する必要があるように聞こえます(関数が他の方向で> Xに変化すると、エラーに対抗するための回転と見なされます)。

どのフレームで回転するかが分かれば、圧力が最も硬かったフレームと、2つの足の間で最も硬かったフレームがわかります。理論的には、足が最も強く押した2つのフレームがわかり、これらの間隔の平均を計算できます。

その後、私はそれがどの足であるかを決定する問題に行きます!

これは以前と同じツアーです。各足がいつ最も圧力をかけるかを知ることは、決定に役立ちます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.