Як я можу покращити виявлення лапи?


198

Після мого попереднього запитання про пошук пальців ніг у кожної лапи я почав завантажувати інші вимірювання, щоб побачити, як він буде триматися. На жаль, я швидко зіткнувся з проблемою з одним із попередніх кроків: розпізнаванням лап.

Розумієте, моє доказ поняття в основному брало максимальний тиск кожного датчика з часом і почало шукати суму кожного ряду, поки не знайде це! = 0,0 Тоді це робиться так само для стовпців, і як тільки він знайде більше 2 рядків, які знову нульові. Він зберігає мінімальні та максимальні значення рядків та стовпців до деякого індексу.

alt текст

Як видно на малюнку, це працює в більшості випадків досить добре. Однак є багато недоліків у цього підходу (крім того, що він дуже примітивний):

  • У людей може бути «порожниста нога», що означає, що в самому сліді є кілька порожніх рядів. Оскільки я побоювався, що це може трапитися і з (великими) собаками, я чекав щонайменше 2 або 3 порожніх ряду, перш ніж відрізати лапу.

    Це створює проблему, якщо інший контакт, зроблений в іншому стовпці, перш ніж він досягне кількох порожніх рядків, розширюючи таким чином область. Я думаю, я міг би порівняти стовпці і побачити, чи перевищують вони певне значення, вони повинні бути окремими лапками.

  • Проблема загострюється, коли собака зовсім маленька або гуляє більш високим темпом. Що відбувається, це те, що пальці передньої лапи все ще контактують, тоді як пальці задньої лапи починають контактувати в тій же області, що і передня лапа!

    З моїм простим сценарієм він не зможе розділити ці два, тому що йому доведеться визначити, які кадри цієї області належать до якої лапи, в той час як на даний момент я повинен був би дивитися лише на максимальні значення для всіх кадрів.

Приклади того, з чого починається не так:

alt текст alt текст

Тож зараз я шукаю кращого способу розпізнавання та відділення лап (після чого я перейду до проблеми вирішення, яка це лапа!).

Оновлення:

Я роздумував над тим, щоб реалізувати відповідь Джо (дивовижно!), Але у мене виникають труднощі з вилученням фактичних даних про лапу з моїх файлів.

alt текст

Coded_paws показує мені всі різні лапи при застосуванні до зображення максимального тиску (див. Вище). Однак рішення переходить через кожен кадр (для відокремлення лапок, що перекриваються) і встановлює чотири атрибути прямокутника, такі як координати або висота / ширина.

Я не можу зрозуміти, як приймати ці атрибути і зберігати їх у якійсь змінній, яку я можу застосувати до даних вимірювань. Оскільки мені потрібно знати для кожної лапи, яке її розташування, під час яких кадрів, і з'єднати це з якою лапою (передня / задня, ліва / права).

Тож як я можу використовувати атрибути «Прямокутники» для отримання цих значень для кожної лапи?

У моїй загальнодоступній папці Dropbox я використовував вимірювання, які використовував під час налаштування питань ( приклад 1 , приклад 2 , приклад 3 ). Для всіх, хто цікавиться, я також створив блог, щоб тримати вас у курсі :-)


Схоже, вам доведеться відвернутись від алгоритму рядків / стовпців і обмежуєте корисну інформацію.
Тамара Війсман

12
Оце Так! Програмне забезпечення для управління котами?
alxx

Це фактично дані про собаку @alxx ;-) Але так, вони будуть використані для їх діагностики!
Іво Фліпс

4
Чому? (незважаючи на це, це веселіше, не знаючи ...)
Бен Регенспан

Відповіді:


358

Якщо ви просто хочете (пол) суміжні областей, є вже проста реалізація в Python: SciPy «S ndimage.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. Трохи розмийте вхідні дані, щоб переконатися, що лапи мають постійний слід. (Було б ефективніше просто використовувати більш велике ядро ​​( structurekwarg для різних scipy.ndimage.morphologyфункцій), але це чомусь не зовсім працює належним чином ...)

  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]. Натомість ми намалюємо прямокутник на основі цих зрізів, на що потрібно трохи більше роботи.


Дві анімації, наведені нижче, показують ваші приклади "Перекриваються лапи" та "Згруповані лапи". Цей метод, здається, працює чудово. (І для того, що б це не було, це працює набагато більш гладко, ніж зображення 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')

Оновлення. Що стосується визначення того, яка лапа контактує з датчиком у будь-який час, найпростішим рішенням є просто зробити той же аналіз, але використовувати всі дані одразу. . зовсім.

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

alt текст


alt текст


alt текст


82
Я навіть не можу почати пояснювати, як приголомшливо ти відповідаєш!
Іво Фліпс

1
@ Ivo: Так, мені б дуже подобалося відмовитись від Джо ще :), але чи варто починати нове запитання, чи, можливо, @Joe, якщо ви хочете, відповісти тут? stackoverflow.com/questions/2546780 / ...
unutbu

2
Я фактично просто викинув .png і зробив convert *.png output.gif. Я, безумовно, мав імагемагік піднести свою машину на коліна раніше, хоча для цього прикладу він працював чудово. Раніше я використовував цей сценарій: svn.effbot.python-hosting.com/pil/Scripts/gifmaker.py, щоб безпосередньо написати анімований gif з python, не зберігаючи окремі кадри. Сподіваюся, що це допомагає! Я опублікую приклад у запитанні @unutbu.
Джо Кінгтон

1
Дякую за інформацію, @Joe. Частина моєї проблеми полягала bbox_inches='tight'в тому plt.savefig, що я не використовував , а інша - нетерплячість :)
unutbu

4
Свята корова, я просто повинен сказати собі, як велика ця відповідь.
andersoj

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 в іншому напрямку, це вважається поворотом на зустрічну помилку).

Якщо ви знаєте, на яких кадрах він обертається, ви дізнаєтесь, на якій рамці тиск був найсильнішим, і ви дізнаєтесь, де було найменш жорстко між двома лапами. Теоретично ви б тоді знали два кадри, де лапи натискали найсильніше і могли обчислити середнє значення цих інтервалів.

після чого я перейду до проблеми вирішення, яка це лапа!

Це та сама екскурсія, як і раніше, адже коли ви застосовуєте найбільше тиск, кожна лапа допомагає вам визначитися.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.