Baik! Saya akhirnya berhasil mendapatkan sesuatu yang bekerja secara konsisten! Masalah ini menarik saya selama beberapa hari ... Hal-hal menyenangkan! Maaf untuk panjangnya jawaban ini, tetapi saya perlu menjelaskan sedikit tentang beberapa hal ... (Meskipun saya dapat membuat rekor untuk jawaban stackoverflow non-spam terpanjang yang pernah ada!)
Sebagai catatan tambahan, saya menggunakan kumpulan data lengkap yang ditautkan oleh Ivo dalam pertanyaan aslinya . Ini adalah serangkaian file rar (satu per anjing) yang masing-masing berisi beberapa percobaan berbeda yang disimpan sebagai array ascii. Daripada mencoba menyalin-tempel contoh kode yang berdiri sendiri ke dalam pertanyaan ini, berikut adalah repositori mercurial bitbucket dengan kode lengkap yang berdiri sendiri. Anda dapat mengkloningnya dengan
hg clone https://joferkington@bitbucket.org/joferkington/paw-analysis
Gambaran
Pada dasarnya ada dua cara untuk mendekati masalah, seperti yang Anda catat dalam pertanyaan Anda. Saya sebenarnya akan menggunakan keduanya dengan cara yang berbeda.
- Gunakan urutan (temporal dan spasial) tumbukan kaki untuk menentukan kaki yang mana.
- Cobalah untuk mengidentifikasi "jejak kaki" hanya berdasarkan bentuknya.
Pada dasarnya, metode pertama bekerja dengan cakar anjing mengikuti pola seperti trapesium yang ditunjukkan pada pertanyaan Ivo di atas, tetapi gagal jika cakarnya tidak mengikuti pola tersebut. Ini cukup mudah untuk dideteksi secara terprogram ketika itu tidak berfungsi.
Oleh karena itu, kita dapat menggunakan pengukuran di mana ia bekerja untuk membangun set data pelatihan (dari ~ 2000 dampak kaki dari ~ 30 anjing yang berbeda) untuk mengenali kaki mana, dan masalahnya berkurang ke klasifikasi yang diawasi (Dengan beberapa kerutan tambahan. .. Pengenalan gambar sedikit lebih sulit daripada masalah klasifikasi terbimbing "normal").
Analisis Pola
Untuk menguraikan metode pertama, ketika seekor anjing berjalan (tidak berlari!) Secara normal (yang mungkin tidak dilakukan oleh beberapa anjing ini), kami memperkirakan cakar akan menabrak dalam urutan: Kiri Depan, Kanan Belakang, Kanan Depan, Kiri Belakang , Kiri Depan, dll. Polanya dapat dimulai dengan kaki kanan depan atau kiri depan.
Jika ini selalu terjadi, kita cukup mengurutkan dampak dengan waktu kontak awal dan menggunakan modulo 4 untuk mengelompokkannya dengan kaki.
Namun, meskipun semuanya "normal", ini tidak berhasil. Ini karena pola bentuknya yang seperti trapesium. Cakar belakang secara spasial berada di belakang cakar depan sebelumnya.
Oleh karena itu, tumbukan kaki belakang setelah benturan kaki depan sering kali jatuh dari pelat sensor, dan tidak terekam. Demikian pula, benturan kaki terakhir sering kali bukan pukulan kaki berikutnya dalam urutan, karena benturan kaki sebelum terjadi dari pelat sensor dan tidak direkam.
Meskipun demikian, kita dapat menggunakan bentuk pola tumbukan kaki untuk menentukan kapan hal ini terjadi, dan apakah kita telah memulai dengan kaki depan kiri atau kanan. (Saya sebenarnya mengabaikan masalah dengan dampak terakhir di sini. Namun, tidak terlalu sulit untuk menambahkannya.)
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
Terlepas dari semua ini, sering kali tidak berfungsi dengan benar. Banyak anjing dalam kumpulan data lengkap tampak berlari, dan dampak cakar tidak mengikuti urutan temporal yang sama seperti saat anjing berjalan. (Atau mungkin anjing itu hanya memiliki masalah pinggul yang parah ...)
Untungnya, kami masih dapat mendeteksi secara programatik apakah dampak kaki mengikuti pola spasial yang kami harapkan atau tidak:
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
Oleh karena itu, meskipun klasifikasi spasial sederhana tidak berfungsi sepanjang waktu, kami dapat menentukan kapan klasifikasi tersebut berfungsi dengan keyakinan yang wajar.
Set Data Pelatihan
Dari klasifikasi berbasis pola yang berfungsi dengan benar, kita dapat membuat kumpulan data pelatihan yang sangat besar dari kaki yang diklasifikasikan dengan benar (~ 2400 dampak kaki dari 32 anjing yang berbeda!).
Sekarang kita dapat mulai melihat seperti apa bentuk kaki kiri depan "rata-rata", dll.
Untuk melakukan ini, kita memerlukan semacam "metrik kaki" yang memiliki dimensi yang sama untuk anjing mana pun. (Dalam kumpulan data lengkap, ada anjing yang sangat besar dan sangat kecil!) Cetakan kaki dari elkhound Irlandia akan jauh lebih lebar dan jauh "lebih berat" daripada cetakan kaki dari pudel mainan. Kita perlu mengubah skala setiap cetakan kaki sehingga a) mereka memiliki jumlah piksel yang sama, dan b) nilai tekanan distandarisasi. Untuk melakukan ini, saya mencontoh ulang setiap cetakan kaki ke kisi 20x20 dan menskalakan ulang nilai tekanan berdasarkan nilai tekanan maksimum, mininum, dan rata-rata untuk dampak kaki.
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
Setelah semua ini, akhirnya kita bisa melihat seperti apa kaki kiri depan, belakang kanan, dll. Perhatikan bahwa ini dirata-ratakan pada> 30 anjing dengan ukuran yang sangat berbeda, dan kami tampaknya mendapatkan hasil yang konsisten!
Namun, sebelum kita melakukan analisis apa pun tentang ini, kita perlu mengurangi mean (kaki rata-rata untuk semua kaki semua anjing).
Sekarang kita dapat menganalisis perbedaan dari mean, yang sedikit lebih mudah dikenali:
Pengenalan Kaki Berbasis Gambar
Oke ... Akhirnya kita memiliki serangkaian pola yang bisa kita coba untuk mencocokkan cakarnya. Setiap kaki dapat diperlakukan sebagai vektor 400 dimensi (dikembalikan oleh paw_image
fungsi) yang dapat dibandingkan dengan empat vektor 400 dimensi ini.
Sayangnya, jika kita hanya menggunakan algoritma klasifikasi terbimbing "normal" (yaitu menemukan pola mana dari 4 pola yang paling dekat dengan cetakan kaki tertentu menggunakan jarak sederhana), itu tidak bekerja secara konsisten. Faktanya, ini tidak jauh lebih baik daripada peluang acak pada set data pelatihan.
Ini adalah masalah umum dalam pengenalan gambar. Karena dimensi tinggi dari data masukan, dan sifat gambar yang agak "kabur" (yaitu piksel yang berdekatan memiliki kovarian yang tinggi), hanya melihat perbedaan gambar dari gambar template tidak memberikan ukuran yang sangat baik untuk kesamaan bentuk mereka.
Eigenpaws
Untuk menyiasati hal ini kita perlu membangun satu set "eigenpaws" (seperti "eigenfaces" dalam pengenalan wajah), dan mendeskripsikan setiap cetakan kaki sebagai kombinasi dari eigenpaws ini. Ini identik dengan analisis komponen utama, dan pada dasarnya menyediakan cara untuk mengurangi dimensi data kita, sehingga jarak adalah ukuran bentuk yang baik.
Karena kita memiliki lebih banyak gambar latihan daripada dimensi (2400 vs 400), tidak perlu menggunakan aljabar linier "mewah" untuk kecepatan. Kita dapat bekerja secara langsung dengan matriks kovarians dari kumpulan data pelatihan:
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
Ini basis_vecs
adalah "eigenpaws".
Untuk menggunakan ini, kita cukup memberi titik (yaitu perkalian matriks) setiap gambar kaki (sebagai vektor 400 dimensi, bukan gambar 20x20) dengan vektor basis. Ini memberi kita vektor 50 dimensi (satu elemen per vektor basis) yang dapat kita gunakan untuk mengklasifikasikan gambar. Alih-alih membandingkan gambar 20x20 dengan gambar 20x20 dari setiap kaki "kerangka", kami membandingkan gambar 50 dimensi yang diubah dengan setiap kaki kerangka yang diubah 50 dimensi. Ini jauh kurang sensitif terhadap variasi kecil dalam bagaimana tepatnya setiap jari kaki diposisikan, dll, dan pada dasarnya mengurangi dimensi masalah hanya ke dimensi yang relevan.
Klasifikasi Kaki Berbasis Eigenpaw
Sekarang kita dapat menggunakan jarak antara vektor 50-dimensi dan vektor "template" untuk setiap kaki untuk mengklasifikasikan kaki mana yang:
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()]
Inilah beberapa hasilnya:
Masalah yang Tersisa
Masih ada beberapa masalah, terutama dengan anjing yang terlalu kecil untuk membuat jejak kaki yang jelas ... (Ini bekerja paling baik dengan anjing besar, karena jari-jari kaki lebih jelas dipisahkan pada resolusi sensor.) Selain itu, jejak kaki parsial tidak dikenali dengan ini sistem, sementara mereka bisa dengan sistem berbasis pola trapesium.
Namun, karena analisis eigenpaw secara inheren menggunakan metrik jarak, kita dapat mengklasifikasikan kaki dengan dua cara, dan kembali ke sistem berbasis pola trapesium ketika jarak terkecil analisis eigenpaw dari "buku kode" melebihi beberapa ambang batas. Saya belum menerapkan ini.
Fiuh ... Itu lama sekali! Topi saya turun ke Ivo karena memiliki pertanyaan yang menyenangkan!