Làm cách nào để cải thiện khả năng phát hiện chân của tôi?


199

Sau câu hỏi trước đó của tôi về việc tìm ngón chân trong mỗi bàn chân , tôi bắt đầu tải lên các phép đo khác để xem nó sẽ giữ như thế nào. Thật không may, tôi nhanh chóng gặp phải một vấn đề với một trong những bước trước đó: nhận dạng các bàn chân.

Bạn thấy đấy, bằng chứng về khái niệm của tôi về cơ bản lấy áp suất tối đa của mỗi cảm biến theo thời gian và sẽ bắt đầu tìm kiếm tổng của mỗi hàng, cho đến khi nó tìm thấy trên đó! = 0.0. Sau đó, nó làm tương tự cho các cột và ngay sau khi nó tìm thấy nhiều hơn 2 hàng có số 0 một lần nữa. Nó lưu trữ các giá trị hàng và cột tối thiểu và lớn nhất vào một số chỉ mục.

văn bản thay thế

Như bạn có thể thấy trong hình, điều này hoạt động khá tốt trong hầu hết các trường hợp. Tuy nhiên, có rất nhiều nhược điểm đối với cách tiếp cận này (ngoài việc rất sơ khai):

  • Con người có thể có 'bàn chân rỗng', nghĩa là có một số hàng trống trong bản thân dấu chân. Vì tôi lo sợ điều này cũng có thể xảy ra với những con chó (lớn), nên tôi đã đợi ít nhất 2 hoặc 3 hàng trống trước khi cắt chân.

    Điều này tạo ra một vấn đề nếu một liên hệ khác được thực hiện trong một cột khác trước khi nó đến một số hàng trống, do đó mở rộng khu vực. Tôi nghĩ rằng tôi có thể so sánh các cột và xem nếu chúng vượt quá một giá trị nhất định, chúng phải là các chân riêng biệt.

  • Vấn đề trở nên tồi tệ hơn khi con chó còn rất nhỏ hoặc đi với tốc độ cao hơn. Điều gì xảy ra là các ngón chân của móng trước vẫn tiếp xúc, trong khi các ngón chân sau chỉ bắt đầu tiếp xúc trong cùng một khu vực với chân trước!

    Với tập lệnh đơn giản của tôi, nó sẽ không thể tách hai điều này, bởi vì nó sẽ phải xác định khung nào của khu vực đó thuộc về chân nào, trong khi hiện tại tôi chỉ phải xem xét các giá trị tối đa trên tất cả các khung.

Ví dụ về nơi bắt đầu sai:

văn bản thay thế văn bản thay thế

Vì vậy, bây giờ tôi đang tìm một cách tốt hơn để nhận biết và tách các chân (sau đó tôi sẽ đi đến vấn đề quyết định đó là chân nào!).

Cập nhật:

Tôi đã mày mò để triển khai câu trả lời (tuyệt vời!) Của Joe, nhưng tôi đang gặp khó khăn khi trích xuất dữ liệu chân thực từ các tệp của mình.

văn bản thay thế

Các coded_paws cho tôi thấy tất cả các bàn chân khác nhau, khi áp dụng cho hình ảnh áp suất cực đại (xem ở trên). Tuy nhiên, giải pháp đi qua từng khung (để tách các bàn chân chồng lên nhau) và đặt bốn thuộc tính Hình chữ nhật, chẳng hạn như tọa độ hoặc chiều cao / chiều rộng.

Tôi không thể tìm ra cách lấy các thuộc tính này và lưu trữ chúng trong một số biến mà tôi có thể áp dụng cho dữ liệu đo lường. Vì tôi cần biết đối với từng con tốt, vị trí của nó là gì trong các khung hình và ghép nối nó với con tốt của nó (trước / sau, trái / phải).

Vậy làm cách nào để tôi có thể sử dụng các thuộc tính Hình chữ nhật để trích xuất các giá trị này cho mỗi chân?

Tôi có các phép đo mà tôi đã sử dụng trong thiết lập câu hỏi trong thư mục Dropbox công khai của mình ( ví dụ 1 , ví dụ 2 , ví dụ 3 ). Đối với bất kỳ ai quan tâm, tôi cũng thiết lập một blog để cập nhật cho bạn :-)


Có vẻ như bạn sẽ phải từ bỏ thuật toán hàng / cột vì bạn đang hạn chế thông tin hữu ích.
Tamara Wijsman

12
Chà! Phần mềm kiểm soát mèo?
alxx

Thực ra đó là dữ liệu về chó @alxx ;-) Nhưng vâng, nó sẽ được sử dụng để chẩn đoán chúng!
Ivo Flipse

4
Tại sao? (
đừng bận tâm

Câu trả lời:


359

Nếu bạn chỉ muốn các vùng liền kề (bán), đã có một cách triển khai dễ dàng trong mô-đun ndimage.morphology của Python: SciPy . Đây là một thao tác hình thái học khá phổ biến .


Về cơ bản, bạn có 5 bước:

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. Làm mờ dữ liệu đầu vào một chút để đảm bảo các bàn chân có dấu chân liên tục. (Sẽ hiệu quả hơn nếu chỉ sử dụng một nhân lớn hơn ( structurekwarg cho các scipy.ndimage.morphologychức năng khác nhau ) nhưng điều này không hoàn toàn hoạt động bình thường vì một số lý do ...)

  2. Đặt ngưỡng cho mảng để bạn có một mảng boolean ở những nơi mà áp suất vượt quá một số giá trị ngưỡng (tức là thresh = data > value)

  3. Lấp các lỗ bên trong để bạn có các vùng sạch hơn ( filled = sp.ndimage.morphology.binary_fill_holes(thresh))

  4. Tìm các vùng tiếp giáp riêng biệt ( coded_paws, num_paws = sp.ndimage.label(filled)). Điều này trả về một mảng với các vùng được mã hóa bằng số (mỗi vùng là vùng liền kề của một số nguyên duy nhất (1 tối đa số bàn chân) với các số không ở mọi nơi khác)).

  5. Cách ly các vùng tiếp giáp bằng cách sử dụng data_slices = sp.ndimage.find_objects(coded_paws). Thao tác này trả về danh sách các bộ giá trị sliceđối tượng, vì vậy bạn có thể lấy vùng dữ liệu cho mỗi bộ với [data[x] for x in data_slices]. Thay vào đó, chúng ta sẽ vẽ một hình chữ nhật dựa trên những lát cắt này, việc này sẽ mất nhiều công hơn một chút.


Hai hoạt ảnh bên dưới hiển thị dữ liệu mẫu về "Các bàn chân chồng chéo" và "Các bàn chân được nhóm" của bạn. Phương pháp này dường như đang hoạt động hoàn hảo. (Và đối với bất kỳ giá trị nào của nó, điều này chạy trơn tru hơn nhiều so với các ảnh GIF bên dưới trên máy của tôi, vì vậy thuật toán phát hiện chân khá nhanh ...)

Bàn chân chồng chéo Các bàn chân được nhóm lại


Đây là một ví dụ đầy đủ (bây giờ có giải thích chi tiết hơn nhiều). Phần lớn việc này là đọc đầu vào và tạo hoạt ảnh. Việc phát hiện con tốt chỉ là 5 dòng mã.

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

Cập nhật: Đối với việc xác định con nào tiếp xúc với cảm biến vào thời điểm nào, giải pháp đơn giản nhất là chỉ thực hiện phân tích tương tự, nhưng sử dụng tất cả dữ liệu cùng một lúc. (tức là xếp dữ liệu đầu vào vào một mảng 3D và làm việc với nó, thay vì các khung thời gian riêng lẻ.) Vì các hàm ndimage của SciPy có nghĩa là hoạt động với các mảng n chiều, chúng tôi không phải sửa đổi hàm tìm chân gốc. ở tất cả.

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

văn bản thay thế


văn bản thay thế


văn bản thay thế


83
Tôi thậm chí không thể bắt đầu giải thích câu trả lời của bạn tuyệt vời như thế nào!
Ivo Flipse

1
@Ivo: Vâng, tôi cũng sẽ thích Joe thêm nữa :) nhưng tôi có nên bắt đầu một câu hỏi mới không, hoặc có lẽ là @Joe, nếu bạn muốn, hãy trả lời ở đây? stackoverflow.com/questions/2546780/…
unutbu

2
Tôi thực sự vừa loại bỏ .png's, và đã làm một convert *.png output.gif. Tôi chắc chắn đã có imagemagick đưa máy của tôi lên đầu gối trước đó, mặc dù nó hoạt động tốt cho ví dụ này. Trước đây, tôi đã sử dụng tập lệnh này: svn.effbot.python-hosting.com/pil/Scripts/gifmaker.py để viết trực tiếp ảnh gif động từ python mà không cần lưu các khung hình riêng lẻ. Hy vọng rằng sẽ giúp! Tôi sẽ đăng một ví dụ về câu hỏi mà @unutbu đã đề cập.
Joe Kington

1
Cảm ơn vì thông tin, @Joe. Một phần của vấn đề của tôi đã được bỏ qua để sử dụng bbox_inches='tight'trong plt.savefig, người kia là thiếu kiên nhẫn :)
unutbu

4
Thánh bò, tôi chỉ có thể nói wow cách lớn câu trả lời này là.
andersoj

4

Tôi không phải là chuyên gia về phát hiện hình ảnh và tôi không biết Python, nhưng tôi sẽ đánh giá cao ...

Để phát hiện các bàn chân riêng lẻ, trước tiên bạn chỉ nên chọn mọi thứ có áp suất lớn hơn một số ngưỡng nhỏ, rất gần với áp suất không hề nhỏ. Mọi pixel / điểm nằm trên điểm này phải được "đánh dấu". Sau đó, mọi pixel liền kề với tất cả các pixel "được đánh dấu" sẽ được đánh dấu và quá trình này được lặp lại một vài lần. Các khối được kết nối hoàn toàn sẽ được hình thành, vì vậy bạn có các đối tượng riêng biệt. Sau đó, mỗi "đối tượng" có giá trị x và y tối thiểu và lớn nhất, vì vậy các hộp giới hạn có thể được đóng gói gọn gàng xung quanh chúng.

Mã giả:

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

Điều đó nên làm điều đó.


0

Lưu ý: Tôi nói là pixel, nhưng đây có thể là các khu vực sử dụng trung bình các pixel. Tối ưu hóa là một vấn đề khác ...

Nghe có vẻ như bạn cần phân tích một chức năng (áp lực theo thời gian) cho mỗi pixel và xác định vị trí của chức năng (khi nó thay đổi> X theo hướng khác, nó được coi là lượt để chống lại lỗi).

Nếu bạn biết nó quay ở khung nào, bạn sẽ biết khung có áp lực mạnh nhất và bạn sẽ biết nơi nào là khó nhất giữa hai bàn chân. Về lý thuyết, sau đó bạn sẽ biết hai khung hình mà các bàn chân ấn mạnh nhất và có thể tính toán trung bình của các khoảng đó.

sau đó tôi sẽ đi đến vấn đề quyết định đó là con nào!

Đây là chuyến tham quan giống như lần trước, biết khi nào mỗi con áp lực nhất sẽ giúp bạn quyết định.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.