简体   繁体   English

如何改善我的爪子检测?

[英]How can I improve my paw detection?

After my previous question on finding toes within each paw , I started loading up other measurements to see how it would hold up. 在我之前关于在每个爪子中发现脚趾的问题之后,我开始加载其他测量值以查看它将如何保持。 Unfortunately, I quickly ran into a problem with one of the preceding steps: recognizing the paws. 不幸的是,我很快就遇到了上述步骤之一的问题:识别爪子。

You see, my proof of concept basically took the maximal pressure of each sensor over time and would start looking for the sum of each row, until it finds on that != 0.0. 你看,我的概念证明基本上是随着时间推移每个传感器的最大压力,并开始寻找每一行的总和,直到它找到!= 0.0。 Then it does the same for the columns and as soon as it finds more than 2 rows with that are zero again. 然后它对列进行相同的操作,一旦找到超过2行,再次为零。 It stores the minimal and maximal row and column values to some index. 它将最小和最大行和列值存储到某个索引。

替代文字

As you can see in the figure, this works quite well in most cases. 正如您在图中所看到的,这在大多数情况下都能很好地工作。 However, there are a lot of downsides to this approach (other than being very primitive): 但是,这种方法有许多缺点(除了非常原始):

  • Humans can have 'hollow feet' which means there are several empty rows within the footprint itself. 人类可以拥有“空心脚”,这意味着足迹内部有几排空行。 Since I feared this could happen with (large) dogs too, I waited for at least 2 or 3 empty rows before cutting off the paw. 因为我担心这种情况也会发生在(大)狗身上,所以在切断爪子之前,我等待至少2或3个空行。

    This creates a problem if another contact made in a different column before it reaches several empty rows, thus expanding the area. 如果在到达多个空行之前在另一列中创建另一个联系,则会产生问题,从而扩展该区域。 I figure I could compare the columns and see if they exceed a certain value, they must be separate paws. 我想我可以比较列,看看它们是否超过某个值,它们必须是单独的爪子。

  • The problem gets worse when the dog is very small or walks at a higher pace. 当狗很小或走得更快时,问题会变得更糟。 What happens is that the front paw's toes are still making contact, while the hind paw's toes just start to make contact within the same area as the front paw! 发生的事情是,前爪的脚趾仍在接触,而后爪的脚趾刚刚开始与前爪在同一区域内接触!

    With my simple script, it won't be able to split these two, because it would have to determine which frames of that area belong to which paw, while currently I would only have to look at the maximal values over all frames. 使用我的简单脚本,它将无法拆分这两个,因为它必须确定该区域的哪些帧属于哪个爪子,而目前我只需要查看所有帧的最大值。

Examples of where it starts going wrong: 它开始出错的例子:

替代文字替代文字

So now I'm looking for a better way of recognizing and separating the paws (after which I'll get to the problem of deciding which paw it is!). 所以现在我正在寻找一种更好的识别和分离爪子的方法 (之后我将解决决定它是哪个爪子的问题!)。

Update: 更新:

I've been tinkering to get Joe's (awesome!) answer implemented, but I'm having difficulties extracting the actual paw data from my files. 我一直在修补Joe的(真棒!)答案,但是我很难从我的文件中提取实际的爪子数据。

替代文字

The coded_paws shows me all the different paws, when applied to the maximal pressure image (see above). 当应用于最大压力图像时,coded_pa​​ws显示了所有不同的爪子(见上文)。 However, the solution goes over each frame (to separate overlapping paws) and sets the four Rectangle attributes, such as coordinates or height/width. 但是,解决方案遍历每个帧(以分隔重叠的爪子)并设置四个Rectangle属性,例如坐标或高度/宽度。

I can't figure out how to take these attributes and store them in some variable that I can apply to the measurement data. 我无法弄清楚如何获取这些属性并将它们存储在一些我可以应用于测量数据的变量中。 Since I need to know for each paw, what its location is during which frames and couple this to which paw it is (front/hind, left/right). 因为我需要知道每个爪子,它在哪个框架中的位置是什么,并将它连接到哪个爪子(前/后,左/右)。

So how can I use the Rectangles attributes to extract these values for each paw? 那么如何使用Rectangles属性为每个爪子提取这些值呢?

I have the measurements I used in the question setup in my public Dropbox folder ( example 1 , example 2 , example 3 ). 我在我的公共Dropbox文件夹中的问题设置中使用了测量值( 示例1示例2示例3 )。 For anyone interested I also set up a blog to keep you up to date :-) 对于任何有兴趣的人我也建立了一个博客 ,让你保持最新:-)

If you're just wanting (semi) contiguous regions, there's already an easy implementation in Python: SciPy 's ndimage.morphology module. 如果您只是想要(半)连续区域,那么在Python中已经有了一个简单的实现: SciPyndimage.morphology模块。 This is a fairly common image morphology operation. 这是一种相当常见的图像形态学操作。


Basically, you have 5 steps: 基本上,你有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. Blur the input data a bit to make sure the paws have a continuous footprint. 稍微模糊输入数据以确保爪子具有连续的足迹。 (It would be more efficient to just use a larger kernel (the structure kwarg to the various scipy.ndimage.morphology functions) but this isn't quite working properly for some reason...) (使用更大的内核( structure kwarg到各种scipy.ndimage.morphology函数)会更有效但是由于某种原因这不太正常......)

  2. Threshold the array so that you have a boolean array of places where the pressure is over some threshold value (ie thresh = data > value ) 阈值数组,以便你有一个布尔数组的压力超过某个阈值的地方(即thresh = data > value

  3. Fill any internal holes, so that you have cleaner regions ( filled = sp.ndimage.morphology.binary_fill_holes(thresh) ) 填充任何内部孔,以便您有更清晰的区域( filled = sp.ndimage.morphology.binary_fill_holes(thresh)

  4. Find the separate contiguous regions ( coded_paws, num_paws = sp.ndimage.label(filled) ). 找到单独的连续区域( coded_paws, num_paws = sp.ndimage.label(filled) )。 This returns an array with the regions coded by number (each region is a contiguous area of a unique integer (1 up to the number of paws) with zeros everywhere else)). 这将返回一个数组,其中的区域由数字编码(每个区域是一个唯一整数的连续区域(1到爪子的数量),其他地方都是零))。

  5. Isolate the contiguous regions using data_slices = sp.ndimage.find_objects(coded_paws) . 使用data_slices = sp.ndimage.find_objects(coded_paws)隔离连续区域。 This returns a list of tuples of slice objects, so you could get the region of the data for each paw with [data[x] for x in data_slices] . 这将返回slice对象元组的列表,因此您可以使用[data[x] for x in data_slices]每个爪子的数据区域。 Instead, we'll draw a rectangle based on these slices, which takes slightly more work. 相反,我们将基于这些切片绘制一个矩形,这需要稍微多一些工作。


The two animations below show your "Overlapping Paws" and "Grouped Paws" example data. 下面的两个动画显示了您的“重叠爪子”和“Grouped Paws”示例数据。 This method seems to be working perfectly. 这种方法似乎工作得很好。 (And for whatever it's worth, this runs much more smoothly than the GIF images below on my machine, so the paw detection algorithm is fairly fast...) (无论它的价值如何,这比我机器上的GIF图像运行得更顺畅,所以爪子检测算法相当快......)

重叠的爪子分组的爪子


Here's a full example (now with much more detailed explanations). 这是一个完整的例子(现在有更详细的解释)。 The vast majority of this is reading the input and making an animation. 绝大多数是阅读输入和制作动画。 The actual paw detection is only 5 lines of code. 实际的爪子检测只有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')

Update: As far as identifying which paw is in contact with the sensor at what times, the simplest solution is to just do the same analysis, but use all of the data at once. 更新:至于在什么时间确定哪个爪子与传感器接触,最简单的解决方案是进行相同的分析,但一次使用所有数据。 (ie stack the input into a 3D array, and work with it, instead of the individual time frames.) Because SciPy's ndimage functions are meant to work with n-dimensional arrays, we don't have to modify the original paw-finding function at all. (即将输入堆叠成3D数组,并使用它,而不是单独的时间帧。)因为SciPy的ndimage函数用于处理n维数组,所以我们不必修改原始的paw-finding函数一点都不

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

替代文字


替代文字


替代文字

I'm no expert in image detection, and I don't know Python, but I'll give it a whack... 我不是图像检测方面的专家,我不懂Python,但我会给它一个打击......

To detect individual paws, you should first only select everything with a pressure greater than some small threshold, very close to no pressure at all. 要检测单个爪子,首先应选择压力大于某个小阈值的所有物体,非常接近无压力。 Every pixel/point that is above this should be "marked." 应该“标记”高于此值的每个像素/点。 Then, every pixel adjacent to all "marked" pixels becomes marked, and this process is repeated a few times. 然后,与所有“标记”像素相邻的每个像素被标记,并且该过程重复几次。 Masses that are totally connected would be formed, so you have distinct objects. 将形成完全连接的质量,因此您有不同的对象。 Then, each "object" has a minimum and maximum x and y value, so bounding boxes can be packed neatly around them. 然后,每个“对象”具有最小和最大x和y值,因此可以围绕它们整齐地打包边界框。

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.

That should about do it. 应该这样做。

Note: I say pixel, but this could be regions using an average of the pixels. 注意:我说像素,但这可能是使用平均像素的区域。 Optimization is another issue... 优化是另一个问题......

Sounds like you need to analyze a function (pressure over time) for each pixel and determine where the function turns (when it changes > X in the other direction it is considered a turn to counter errors). 听起来你需要分析每个像素的一个函数(压力随时间变化)并确定函数转向的位置 (当它在另一个方向上改变> X时,它被视为转向反击错误)。

If you know at what frames it turns, you will know the frame where the pressure was the most hard and you will know where it was the least hard between the two paws. 如果你知道它转动的是什么样的框架,你就会知道压力最大的框架,你会知道两只爪子之间的硬度最小。 In theory, you then would know the two frames where the paws pressed the most hard and can calculate an average of those intervals. 从理论上讲,你可以知道爪子压得最厉害的两个框架,并可以计算出这些间隔的平均值。

after which I'll get to the problem of deciding which paw it is! 之后我会遇到决定它是哪个爪子的问题!

This is the same tour as before, knowing when each paw applies the most pressure helps you decide. 这是与以前相同的旅行,知道每个爪子施加最大压力有助于您决定。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM