Pençe algılamamı nasıl geliştirebilirim?


198

Her pençede ayak parmakları bulma ile ilgili önceki sorumdan sonra , nasıl dayanacağını görmek için diğer ölçümleri yüklemeye başladım. Ne yazık ki, önceki adımlardan biriyle hızlıca bir sorunla karşılaştım: pençeleri tanımak.

Görüyorsunuz, kavram kanıtım temelde zaman içinde her bir sensörün maksimum basıncını aldı ve üzerinde bulunana kadar her sıranın toplamını aramaya başlayacaktı! = 0.0. Sonra sütunlar için aynı şeyi yapar ve 2 sıfırdan fazla satır bulur bulmaz tekrar sıfır olur. Minimum ve maksimum satır ve sütun değerlerini bir dizine kaydeder.

alternatif metin

Şekilde gördüğünüz gibi, bu çoğu durumda oldukça iyi çalışır. Bununla birlikte, bu yaklaşımın bir çok dezavantajı vardır (çok ilkel olmaktan başka):

  • İnsanlar 'içi boş ayaklara' sahip olabilir, yani ayak izinin içinde birkaç boş satır vardır. Bunun (büyük) köpeklerde de olabileceğinden korktuğum için, pençeyi kesmeden önce en az 2 veya 3 boş satır bekledim.

    Bu, farklı bir sütunda birkaç boş satıra ulaşmadan başka bir kişinin yaptığı ve böylece alanı genişleten bir sorun yaratır. Sütunları karşılaştırabildiğimi ve belirli bir değeri aşıp aşmadıklarını görebileceğimi düşünüyorum, ayrı pençeler olmalılar.

  • Köpek çok küçük olduğunda veya daha yüksek bir hızda yürürken sorun daha da kötüleşir. Olan şey, ön pençenin ayak parmakları hala temas halindeyken, arka pençenin ayak parmakları ön pençeyle aynı alanda temas etmeye başlar!

    Benim basit komut dosyasıyla, bu ikisini bölemeyecek, çünkü o alanın hangi çerçevelerinin hangi pençeye ait olduğunu belirlemek zorunda kalacak, şu anda sadece tüm çerçevelerdeki maksimum değerlere bakmak zorunda kalacağım.

Yanlış gitmeye başladığı yerlere örnekler:

alternatif metin alternatif metin

Şimdi pençeleri tanımanın ve ayırmanın daha iyi bir yolunu arıyorum (bundan sonra hangi pençenin olduğuna karar verme sorununa ulaşacağım!).

Güncelleme:

Joe'nun (müthiş!) Cevabını uygulamaya koymak için uğraşıyordum, ancak dosyalarımdaki gerçek pençe verilerini ayıklamakta zorlanıyorum.

alternatif metin

Coded_paws, maksimum basınç görüntüsüne uygulandığında bana tüm farklı pençeleri gösterir (yukarıya bakın). Ancak, çözüm her karenin üzerinden geçer (çakışan pençeleri ayırmak için) ve koordinatlar veya yükseklik / genişlik gibi dört Dikdörtgen niteliğini ayarlar.

Bu öznitelikleri nasıl alacağımı ve ölçüm verilerine uygulayabileceğim bazı değişkenlerde nasıl saklayacağımı anlayamıyorum. Her pençe için bilmek zorunda olduğum için, hangi çerçeveler sırasında hangi çerçevenin ne olduğunu ve bu pençeyi birleştirdiğini (ön / arka, sol / sağ).

Öyleyse her bir pençe için bu değerleri ayıklamak için Dikdörtgenler özniteliklerini nasıl kullanabilirim?

Genel Dropbox klasörümde ( örnek 1 , örnek 2 , örnek 3 ) soru kurulumunda kullandığım ölçümler var . İlgilenen herkes için sizi güncel tutmak için bir blog da kurdum :-)


Yararlı bilgileri sınırladığınız bir satır / sütun algoritmasından uzaklaşmanız gerekecek gibi görünüyor.
Tamara Wijsman

12
Vaov! Cat kontrol yazılımı?
alxx

Köpek verisi aslında @alxx ;-) Ama evet, onları teşhis etmek için kullanılacak!
Ivo Flipse

4
Neden? (nevermind, bilmemek daha eğlenceli ...)
Ben Regenspan

Yanıtlar:


358

Sadece (yarı) bitişik bölgeler isteyen konum, zaten Python kolay bir uygulama var: scipy bireyin ndimage.morphology modülü. Bu oldukça yaygın bir görüntü morfolojisi ameliyatıdır.


Temel olarak, 5 adımınız vardır:

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. Pençelerin sürekli bir ayak izi olduğundan emin olmak için giriş verilerini biraz bulanıklaştırın. (Sadece daha büyük bir çekirdek kullanmak (daha structureçeşitli scipy.ndimage.morphologyişlevler için kwarg) daha verimli olurdu , ancak bu bir nedenden dolayı düzgün çalışmıyor ...)

  2. Basıncı bir miktar eşik değerinin (yani thresh = data > value) aştığı bir boolean yer dizisine sahip olacak şekilde diziyi eşleştirin

  3. Daha temiz bölgelere sahip olmak için tüm iç delikleri doldurun ( filled = sp.ndimage.morphology.binary_fill_holes(thresh))

  4. Ayrı bitişik bölgeleri bulun ( coded_paws, num_paws = sp.ndimage.label(filled)). Bu, sayılara göre kodlanmış bölgelere sahip bir dizi döndürür (her bölge, başka bir yerde sıfırlarla birlikte benzersiz bir tamsayının bitişik bir alanıdır (1'e kadar pençe sayısına kadar)).

  5. Bitişik bölgeleri kullanarak izole edin data_slices = sp.ndimage.find_objects(coded_paws). Bu, slicenesnelerin bir listesini döndürür , böylece her bir pençe için verilerin bölgesini alabilirsiniz [data[x] for x in data_slices]. Bunun yerine, biraz daha fazla iş gerektiren bu dilimlere dayanan bir dikdörtgen çizeceğiz.


Aşağıdaki iki animasyon "Çakışan Paws" ve "Gruplandırılmış Paws" örnek verilerinizi gösterir. Bu yöntem mükemmel çalışıyor gibi görünüyor. (Ve ne olursa olsun, bu benim makinemdeki aşağıdaki GIF görüntülerinden çok daha sorunsuz çalışıyor, bu yüzden pençe algılama algoritması oldukça hızlı ...)

Örtüşen Pençeler Gruplandırılmış Pençeler


İşte tam bir örnek (şimdi çok daha ayrıntılı açıklamalar ile). Bunun büyük çoğunluğu girdiyi okumak ve bir animasyon yapmaktır. Gerçek pençe tespiti sadece 5 satırlık koddur.

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

Güncelleme: Hangi pençenin sensörle hangi zamanlarda temas ettiğini tespit etmekle birlikte, en basit çözüm aynı analizi yapmaktır, ancak tüm verileri bir kerede kullanmaktır. (yani girdiyi bir 3D dizisi olarak istifleyin ve ayrı zaman çerçeveleri yerine onunla çalışın.) SciPy'nin ndimage işlevleri n boyutlu dizilerle çalışacağı için orijinal pençe bulma işlevini değiştirmek zorunda değiliz hiç.

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

alternatif metin


alternatif metin


alternatif metin


82
Cevabınızın ne kadar harika olduğunu açıklamaya bile başlayamıyorum!
Ivo Flipse

1
@Ivo: Evet, Joe'yu biraz daha beğenmekten hoşlanıyorum :) ama yeni bir soruya başlamalıyım, yoksa belki de @Joe, burada cevaplasın mı? stackoverflow.com/questions/2546780/…
unutbu

2
Aslında .png'leri boşalttım ve bir convert *.png output.gif. Bu örnek için iyi çalışmasına rağmen, imagemagick'i daha önce dizlerime getirdim. Geçmişte, bu komut dosyasını kullandım: svn.effbot.python-hosting.com/pil/Scripts/gifmaker.py , çerçeveleri tek tek kaydetmeden doğrudan pitondan animasyonlu bir gif yazmak için. Umarım yardımcı olur! @Unutbu'un bahsettiği soruya bir örnek göndereceğim.
Joe Kington

1
Bilgi için teţekkürler @Joe. Benim Sorunun bir bölümü kullanmak ihmal edilmiş bbox_inches='tight'de plt.savefig, diğer sabırsızlık :) oldu
unutbu

4
Kutsal inek, sadece söylemek zorunda vay Bu cevap ne kadar büyük de.
andersoj

4

Görüntü algılama konusunda uzman değilim ve Python'u tanımıyorum, ama bir vuruş yapacağım ...

Tek tek pençeleri tespit etmek için, ilk önce sadece küçük bir eşik değerinden daha yüksek bir basınca sahip, hiçbir basınca çok yakın olmayan her şeyi seçmelisiniz. Bunun üzerindeki her piksel / nokta "işaretlenmelidir". Ardından, tüm "işaretli" piksellere bitişik olan her piksel işaretlenir ve bu işlem birkaç kez tekrarlanır. Tamamen bağlı kitleler oluşacaktı, bu yüzden farklı nesneleriniz var. Daha sonra, her "nesne" minimum ve maksimum x ve y değerine sahiptir, bu nedenle sınırlayıcı kutular etraflarında düzgün bir şekilde paketlenebilir.

pseudocode:

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

Bunu yapmak lazım.


0

Not: Piksel diyorum, ancak bu ortalama piksel kullanan bölgeler olabilir. Optimizasyon başka bir konudur ...

Her piksel için bir işlevi (zaman içindeki basınç) analiz etmeniz ve işlevin nereye döndüğünü belirlemeniz gerekir gerekir (diğer yönde> X değiştiğinde, karşı hatalara dönüş olarak kabul edilir).

Hangi karelerde döndüğünü biliyorsanız, basıncın en zor olduğu kareyi ve iki pençe arasındaki en az zorun nerede olduğunu bileceksiniz. Teorik olarak, pençelerin en sert baskı yaptığı ve bu aralıkların ortalamasını hesaplayabildiği iki kareyi bilirsiniz.

bundan sonra hangi pençe olduğuna karar verme problemine ulaşacağım!

Bu, her turun en fazla baskıyı ne zaman uyguladığını bilmek karar vermenize yardımcı olur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.