Bagaimana saya bisa meningkatkan deteksi kaki saya?

198

Setelah pertanyaan saya sebelumnya di menemukan jari kaki dalam setiap kaki , saya mulai memuat pengukuran lain untuk melihat bagaimana itu akan bertahan. Sayangnya, saya dengan cepat mengalami masalah dengan salah satu langkah sebelumnya: mengenali cakarnya.

Anda lihat, bukti konsep saya pada dasarnya mengambil tekanan maksimal setiap sensor dari waktu ke waktu dan akan mulai mencari jumlah dari setiap baris, sampai menemukan itu! = 0,0. Kemudian ia melakukan hal yang sama untuk kolom dan segera setelah menemukan lebih dari 2 baris dengan nol. Ini menyimpan nilai baris dan kolom minimal dan maksimal ke beberapa indeks.

teks alternatif

Seperti yang dapat Anda lihat pada gambar, ini bekerja cukup baik dalam banyak kasus. Namun, ada banyak kelemahan dalam pendekatan ini (selain sangat primitif):

  • Manusia dapat memiliki 'kaki berlubang' yang berarti ada beberapa baris kosong di dalam tapak itu sendiri. Karena saya khawatir ini bisa terjadi dengan anjing (besar) juga, saya menunggu setidaknya 2 atau 3 baris kosong sebelum memotong cakarnya.

    Ini menciptakan masalah jika kontak lain dibuat di kolom yang berbeda sebelum mencapai beberapa baris kosong, sehingga memperluas area. Saya pikir saya bisa membandingkan kolom dan melihat apakah mereka melebihi nilai tertentu, mereka harus kaki terpisah.

  • Masalahnya menjadi lebih buruk ketika anjing itu sangat kecil atau berjalan dengan kecepatan yang lebih tinggi. Apa yang terjadi adalah bahwa jari kaki kaki depan masih melakukan kontak, sedangkan jari kaki belakangnya baru saja mulai melakukan kontak dalam area yang sama dengan kaki depan!

    Dengan skrip sederhana saya, tidak akan dapat memisahkan keduanya, karena harus menentukan frame mana dari area tersebut yang memiliki paw, sementara saat ini saya hanya perlu melihat nilai maksimal dari semua frame.

Contoh di mana mulai salah:

teks alternatif teks alternatif

Jadi sekarang saya sedang mencari cara yang lebih baik untuk mengenali dan memisahkan cakarnya (setelah itu saya akan sampai pada masalah menentukan mana cakarnya!).

Memperbarui:

Saya telah bermain-main untuk menerapkan jawaban Joe (luar biasa!), Tetapi saya kesulitan mengekstraksi data kaki aktual dari file saya.

teks alternatif

Coded_paws menunjukkan kepada saya semua cakar yang berbeda, ketika diterapkan pada gambar tekanan maksimal (lihat di atas). Namun, solusi melewati setiap frame (untuk memisahkan kaki yang tumpang tindih) dan menetapkan empat atribut Rectangle, seperti koordinat atau tinggi / lebar.

Saya tidak tahu cara mengambil atribut ini dan menyimpannya dalam beberapa variabel yang dapat saya terapkan pada data pengukuran. Karena saya perlu tahu untuk setiap kaki, apa lokasinya selama bingkai dan pasangan ini dengan kaki yang mana (depan / belakang, kiri / kanan).

Jadi bagaimana saya bisa menggunakan atribut Rectangles untuk mengekstraksi nilai-nilai ini untuk setiap kaki?

Saya memiliki pengukuran yang saya gunakan dalam pengaturan pertanyaan di folder Dropbox publik saya ( contoh 1 , contoh 2 , contoh 3 ). Bagi siapa pun yang tertarik, saya juga membuat blog agar Anda selalu mendapatkan informasi terbaru :-)

Ivo Flipse
sumber
Sepertinya Anda harus berpaling dari algoritma baris / kolom dan Anda membatasi informasi yang bermanfaat.
Tamara Wijsman
12
Wow! Perangkat lunak kontrol kucing?
alxx
Sebenarnya ini adalah data anjing @alxx ;-) Tapi ya, itu akan digunakan untuk mendiagnosis mereka!
Ivo Flipse
4
Mengapa? (tidak apa-apa, itu lebih menyenangkan tidak tahu ...)
Ben Regenspan

Jawaban:

358

Jika Anda hanya ingin (semi) daerah berdekatan, sudah ada implementasi yang mudah dengan Python: SciPy 's ndimage.morphology modul. Ini adalah operasi morfologi gambar yang cukup umum .


Pada dasarnya, Anda memiliki 5 langkah:

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. Kaburkan data input sedikit untuk memastikan kaki memiliki jejak kaki yang berkelanjutan. (Akan lebih efisien jika hanya menggunakan kernel yang lebih besar ( structurekwarg untuk berbagai scipy.ndimage.morphologyfungsi) tetapi ini tidak berfungsi dengan baik untuk beberapa alasan ...)

  2. Threshold array sehingga Anda memiliki array boolean tempat di mana tekanan melebihi beberapa nilai ambang (yaitu thresh = data > value)

  3. Isi setiap lubang internal, sehingga Anda memiliki daerah yang lebih bersih ( filled = sp.ndimage.morphology.binary_fill_holes(thresh))

  4. Temukan wilayah berdekatan yang terpisah ( coded_paws, num_paws = sp.ndimage.label(filled)). Ini mengembalikan array dengan wilayah yang dikodekan oleh angka (setiap wilayah adalah area yang berdekatan dari integer unik (1 hingga jumlah kaki) dengan nol di tempat lain)).

  5. Isolasi daerah yang berdekatan menggunakan data_slices = sp.ndimage.find_objects(coded_paws). Ini mengembalikan daftar tupel sliceobjek, sehingga Anda bisa mendapatkan wilayah data untuk setiap kaki [data[x] for x in data_slices]. Sebagai gantinya, kami akan menggambar persegi panjang berdasarkan irisan ini, yang membutuhkan sedikit lebih banyak pekerjaan.


Dua animasi di bawah ini menunjukkan data contoh "Cakar yang Tumpang tindih" dan "Cakar yang Dikelompokkan". Metode ini tampaknya bekerja dengan sempurna. (Dan berapapun nilainya, ini berjalan jauh lebih lancar daripada gambar GIF di bawah pada komputer saya, sehingga algoritma pendeteksian kaki cukup cepat ...)

Kaki yang tumpang tindih Cakar yang dikelompokkan


Inilah contoh lengkap (sekarang dengan penjelasan yang jauh lebih terperinci). Sebagian besar dari ini membaca input dan membuat animasi. Deteksi kaki sebenarnya hanya 5 baris kode.

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')

Pembaruan: Sejauh mengidentifikasi kaki mana yang bersentuhan dengan sensor pada jam berapa, solusi paling sederhana adalah dengan hanya melakukan analisis yang sama, tetapi gunakan semua data sekaligus. (yaitu, menumpuk input ke dalam array 3D, dan bekerja dengannya, alih-alih kerangka waktu individual.) Karena fungsi ndimage SciPy dimaksudkan untuk bekerja dengan array n-dimensi, kita tidak perlu memodifikasi fungsi pencarian paw asli sama sekali.

# 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')

teks alternatif


teks alternatif


teks alternatif

Joe Kington
sumber
82
Saya bahkan tidak bisa mulai menjelaskan betapa hebatnya jawaban Anda!
Ivo Flipse
1
@Ivo: Ya, saya akan menikmati upvoting Joe lagi juga :) tetapi haruskah saya memulai pertanyaan baru, atau mungkin @ Jo, jika Anda mau, jawab di sini? stackoverflow.com/questions/2546780/…
unutbu
2
Saya sebenarnya baru saja membuang .png's, dan melakukan convert *.png output.gif. Aku tentu saja membayangkan imagemagick membuat mesin saya bertekuk lutut sebelumnya, meskipun itu bekerja dengan baik untuk contoh ini. Di masa lalu, saya telah menggunakan skrip ini: svn.effbot.python-hosting.com/pil/Scripts/gifmaker.py untuk secara langsung menulis gif animasi dari python tanpa menyimpan frame individu. Semoga itu bisa membantu! Saya akan memposting contoh di pertanyaan @unutbu yang disebutkan.
Joe Kington
1
Terima kasih atas informasinya, @Joe. Bagian dari masalah saya lalai untuk digunakan bbox_inches='tight'dalam plt.savefig, yang lain adalah ketidaksabaran :)
unutbu
4
Astaga, aku harus bilang wow betapa hebatnya jawaban ini.
andersoj
4

Saya bukan ahli dalam pendeteksian gambar, dan saya tidak tahu Python, tapi saya akan memukulnya ...

Untuk mendeteksi masing-masing cakar, pertama-tama Anda harus memilih semuanya dengan tekanan lebih besar dari beberapa ambang batas kecil, sangat dekat dengan tidak ada tekanan sama sekali. Setiap piksel / titik di atas ini harus ditandai. Kemudian, setiap piksel yang berdekatan dengan semua piksel "ditandai" menjadi ditandai, dan proses ini diulang beberapa kali. Massa yang terhubung sepenuhnya akan terbentuk, sehingga Anda memiliki objek yang berbeda. Kemudian, setiap "objek" memiliki nilai x dan y minimum dan maksimum, sehingga kotak pembatas dapat dikemas dengan rapi di sekitarnya.

Kodesemu:

(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.

Itu harus tentang melakukannya.

TaslemGuy
sumber
0

catatan: Saya katakan piksel, tetapi ini bisa berupa wilayah yang menggunakan rata-rata piksel. Optimalisasi adalah masalah lain ...

Kedengarannya seperti Anda perlu menganalisis fungsi (tekanan dari waktu ke waktu) untuk setiap piksel dan menentukan di mana fungsi berubah (ketika berubah> X di arah lain itu dianggap sebagai giliran untuk mengatasi kesalahan).

Jika Anda tahu pada frame mana ternyata, Anda akan tahu bingkai di mana tekanan adalah yang paling sulit dan Anda akan tahu di mana itu adalah yang paling sulit di antara kedua cakar. Secara teori, Anda kemudian akan tahu dua frame di mana cakar ditekan paling keras dan dapat menghitung rata-rata interval tersebut.

setelah itu saya akan sampai pada masalah menentukan kaki yang mana!

Ini adalah tur yang sama seperti sebelumnya, mengetahui kapan setiap paw menerapkan tekanan paling banyak membantu Anda memutuskan.

Tamara Wijsman
sumber