よし!ようやく、なんとか一貫して動作するようになりました この問題が数日間私を引き込みました...楽しいものです!この回答の長さで申し訳ありませんが、私はいくつかのことについて少し詳しく説明する必要があります...
補足として、私はIvo が元の質問でリンクを提供した完全なデータセットを使用しています。これは、一連のrarファイル(1匹につき1匹)であり、それぞれにASCII配列として保存されたいくつかの異なる実験実行が含まれています。スタンドアロンコードの例をこの質問にコピーアンドペーストするのではなく、完全なスタンドアロンコードを含むbitbucket mercurialリポジトリを次に示します。あなたはそれをクローンすることができます
hg clone https://joferkington@bitbucket.org/joferkington/paw-analysis
概観
質問で述べたように、問題に取り組む方法は基本的に2つあります。実際には両方を異なる方法で使用します。
- 足の影響の(時間的および空間的)順序を使用して、どの足がどれであるかを決定します。
- 純粋にその形状に基づいて「足跡」を識別してみてください。
基本的に、最初の方法は、犬の足が上記のIvoの質問に示されている台形のようなパターンに従う場合に機能しますが、足がそのパターンに従わない場合は常に失敗します。機能しない場合は、プログラムで簡単に検出できます。
したがって、トレーニングデータセット(約30匹の犬による約2000足の影響)を構築して、どの足がどれであるかを認識するために機能した場所の測定値を使用して、問題を監視された分類(追加のしわがある場合)に減らすことができます。 ..画像認識は、「通常の」教師付き分類問題よりも少し難しいです)。
パターン分析
最初の方法を詳しく説明すると、犬が普通に歩いている(走っていない!)とき(これらの犬の一部はそうでない場合があります)、前足、後右、前右、後左の順序で足に影響を与えると予想されます。 、前左など。パターンは前左足または前右足のいずれかで開始できます。
これが常に当てはまる場合は、最初の接触時間で影響を並べ替え、モジュロ4を使用して足でグループ化します。
ただし、すべてが「正常」である場合でも、これは機能しません。これは、パターンの台形のような形状によるものです。後足は前の前足の後ろに空間的に落ちます。
したがって、最初の前足の衝撃後の後ろ足の衝撃は、センサープレートから外れることが多く、記録されません。同様に、足の最後の衝撃は、センサープレートから外れて発生して記録されなかった前の足の衝撃と同様に、多くの場合、シーケンスの次の足ではありません。
それにもかかわらず、足のインパクトパターンの形状を使用して、いつこれが発生したか、および前足が左か右かを確認できます。(ここでは、最後の影響に関する問題は無視しています。ただし、追加するのはそれほど難しくありません。)
def group_paws(data_slices, time):
# Sort slices by initial contact time
data_slices.sort(key=lambda s: s[-1].start)
# Get the centroid for each paw impact...
paw_coords = []
for x,y,z in data_slices:
paw_coords.append([(item.stop + item.start) / 2.0 for item in (x,y)])
paw_coords = np.array(paw_coords)
# Make a vector between each sucessive impact...
dx, dy = np.diff(paw_coords, axis=0).T
#-- Group paws -------------------------------------------
paw_code = {0:'LF', 1:'RH', 2:'RF', 3:'LH'}
paw_number = np.arange(len(paw_coords))
# Did we miss the hind paw impact after the first
# front paw impact? If so, first dx will be positive...
if dx[0] > 0:
paw_number[1:] += 1
# Are we starting with the left or right front paw...
# We assume we're starting with the left, and check dy[0].
# If dy[0] > 0 (i.e. the next paw impacts to the left), then
# it's actually the right front paw, instead of the left.
if dy[0] > 0: # Right front paw impact...
paw_number += 2
# Now we can determine the paw with a simple modulo 4..
paw_codes = paw_number % 4
paw_labels = [paw_code[code] for code in paw_codes]
return paw_labels
これらすべてにもかかわらず、それは頻繁に正しく機能しません。完全なデータセット内の犬の多くは走っているように見え、足の影響は犬が歩いているときと同じ時間的順序に従っていません。(または、おそらく犬は深刻な腰の問題を抱えている...)
幸いにも、足の影響が予想される空間パターンに従うかどうかをプログラムで検出できます。
def paw_pattern_problems(paw_labels, dx, dy):
"""Check whether or not the label sequence "paw_labels" conforms to our
expected spatial pattern of paw impacts. "paw_labels" should be a sequence
of the strings: "LH", "RH", "LF", "RF" corresponding to the different paws"""
# Check for problems... (This could be written a _lot_ more cleanly...)
problems = False
last = paw_labels[0]
for paw, dy, dx in zip(paw_labels[1:], dy, dx):
# Going from a left paw to a right, dy should be negative
if last.startswith('L') and paw.startswith('R') and (dy > 0):
problems = True
break
# Going from a right paw to a left, dy should be positive
if last.startswith('R') and paw.startswith('L') and (dy < 0):
problems = True
break
# Going from a front paw to a hind paw, dx should be negative
if last.endswith('F') and paw.endswith('H') and (dx > 0):
problems = True
break
# Going from a hind paw to a front paw, dx should be positive
if last.endswith('H') and paw.endswith('F') and (dx < 0):
problems = True
break
last = paw
return problems
したがって、単純な空間分類が常に機能するとは限りませんが、妥当な信頼度で機能する場合を判断できます。
トレーニングデータセット
正しく機能したパターンベースの分類から、正しく分類された足の非常に大規模なトレーニングデータセットを構築できます(32種類の犬による2400足の影響!)。
これで、「平均的な」左前部などの足の様子を見ることができます。
これを行うには、犬と同じ次元の「足のメトリック」のようなものが必要です。(完全なデータセットには、非常に大きな犬と非常に小さな犬の両方がいます!)アイリッシュエルクハウンドの足跡は、おもちゃのプードルの足跡よりも幅が広く、重くなります。a)ピクセル数が同じで、b)圧力値が標準化されるように、各足跡を再スケーリングする必要があります。これを行うために、各足跡を20x20グリッドに再サンプリングし、足の衝撃に対する最大、最小、および平均の圧力値に基づいて圧力値を再スケーリングしました。
def paw_image(paw):
from scipy.ndimage import map_coordinates
ny, nx = paw.shape
# Trim off any "blank" edges around the paw...
mask = paw > 0.01 * paw.max()
y, x = np.mgrid[:ny, :nx]
ymin, ymax = y[mask].min(), y[mask].max()
xmin, xmax = x[mask].min(), x[mask].max()
# Make a 20x20 grid to resample the paw pressure values onto
numx, numy = 20, 20
xi = np.linspace(xmin, xmax, numx)
yi = np.linspace(ymin, ymax, numy)
xi, yi = np.meshgrid(xi, yi)
# Resample the values onto the 20x20 grid
coords = np.vstack([yi.flatten(), xi.flatten()])
zi = map_coordinates(paw, coords)
zi = zi.reshape((numy, numx))
# Rescale the pressure values
zi -= zi.min()
zi /= zi.max()
zi -= zi.mean() #<- Helps distinguish front from hind paws...
return zi
これがすべて終わった後、私たちはようやく、平均的な左前、右後などの足の様子を見ることができます。これは、サイズが大きく異なる30匹を超える犬の平均であり、一貫した結果が得られているようです。
ただし、これらの分析を行う前に、平均(すべての犬のすべての脚の平均足)を差し引く必要があります。
これで、平均との違いを分析できるようになりました。これは認識が少し簡単です。
画像ベースの足の認識
OK ...ようやく、足を突き合わせるためのパターンセットができました。各足はpaw_image
、これらの4つの400次元ベクトルと比較できる(関数によって返される)400次元ベクトルとして扱うことができます。
残念ながら、「通常の」監視付き分類アルゴリズムを使用するだけの場合(つまり、4つのパターンのうちどれが特定の足跡に最も近いかを単純な距離を使用して見つける)は、一貫して機能しません。実際、トレーニングデータセットのランダムチャンスよりもはるかに優れています。
これは、画像認識の一般的な問題です。入力データの高次元性と画像のやや「あいまい」な性質(つまり、隣接するピクセルの共分散が高い)のため、テンプレート画像と画像の違いを単に見ただけでは、それらの形の類似性。
アイゲンポーズ
これを回避するには、一連の「eigenpaws」を作成し(顔認識の「eigenfaces」のように)、各足跡をこれらのeigenpawsの組み合わせとして記述する必要があります。これは主成分分析と同じであり、基本的にデータの次元を削減する方法を提供するため、距離は形状の適切な尺度になります。
ディメンション(2400と400)よりもトレーニングイメージが多いため、速度を上げるために「ファンシーな」線形代数を実行する必要はありません。トレーニングデータセットの共分散行列を直接操作できます。
def make_eigenpaws(paw_data):
"""Creates a set of eigenpaws based on paw_data.
paw_data is a numdata by numdimensions matrix of all of the observations."""
average_paw = paw_data.mean(axis=0)
paw_data -= average_paw
# Determine the eigenvectors of the covariance matrix of the data
cov = np.cov(paw_data.T)
eigvals, eigvecs = np.linalg.eig(cov)
# Sort the eigenvectors by ascending eigenvalue (largest is last)
eig_idx = np.argsort(eigvals)
sorted_eigvecs = eigvecs[:,eig_idx]
sorted_eigvals = eigvals[:,eig_idx]
# Now choose a cutoff number of eigenvectors to use
# (50 seems to work well, but it's arbirtrary...
num_basis_vecs = 50
basis_vecs = sorted_eigvecs[:,-num_basis_vecs:]
return basis_vecs
これらbasis_vecs
が「固有足」です。
これらを使用するには、各足の画像(20x20の画像ではなく、400次元のベクトルとして)に基底ベクトルをドット(つまり、行列の乗算)します。これにより、画像の分類に使用できる50次元ベクトル(基底ベクトルごとに1つの要素)が得られます。20x20の画像を各「テンプレート」の足の20x20の画像と比較する代わりに、50次元の変換された画像を各50次元の変換されたテンプレートの足と比較します。これは、各つま先の正確な配置方法などの小さな変動に対する感度がはるかに低く、基本的に問題の次元を関連する次元にまで減らします。
アイゲンポーに基づく足の分類
これで、各脚の50次元ベクトルと「テンプレート」ベクトルの間の距離を使用して、どの足がどれであるかを分類できます。
codebook = np.load('codebook.npy') # Template vectors for each paw
average_paw = np.load('average_paw.npy')
basis_stds = np.load('basis_stds.npy') # Needed to "whiten" the dataset...
basis_vecs = np.load('basis_vecs.npy')
paw_code = {0:'LF', 1:'RH', 2:'RF', 3:'LH'}
def classify(paw):
paw = paw.flatten()
paw -= average_paw
scores = paw.dot(basis_vecs) / basis_stds
diff = codebook - scores
diff *= diff
diff = np.sqrt(diff.sum(axis=1))
return paw_code[diff.argmin()]
結果の一部を次に示します。
残っている問題
特に犬が小さすぎて明確な足跡を作成できない場合は、まだいくつかの問題があります...(足がセンサーの解像度でより明確に分離されているので、大きな犬に最適です)。また、部分的な足跡はこれで認識されません。システム、台形パターンベースのシステムとすることができます。
ただし、固有足分析は本質的に距離メトリックを使用するため、足を両方の方法で分類し、「コードブック」からの固有足分析の最小距離がしきい値を超えている場合は、台形パターンベースのシステムにフォールバックできます。ただし、これはまだ実装していません。
ふew…それは長かった!こんなに楽しい質問をした私の帽子は、Ivoから離れています。