关于Python:我如何改进我的paw检测?

How can I improve my paw detection?

在我之前关于在每个爪子内找到脚趾的问题之后,我开始加载其他测量值,以查看它如何支撑。不幸的是,我很快就遇到了前面其中一个步骤的问题:识别爪子。

你看,我的概念证明基本上是用每一个传感器的最大压力随着时间的推移,然后开始寻找每一行的总和,直到它找到为止!= 0。然后,它对列执行相同的操作,一旦发现超过2行的数据再次为零。它将最小值和最大值的行和列存储到某个索引中。

alt text

正如您在图中看到的,在大多数情况下,这都非常有效。但是,这种方法有很多缺点(不是非常原始):

  • 人类可以有"空心的脚",这意味着在脚印本身有几个空行。因为我担心这种情况也会发生在(大)狗身上,所以我至少等了2到3排空狗才切断了爪子。

    如果另一个联系人在到达多个空行之前在不同的列中进行了接触,则会产生问题,从而扩展该区域。我想我可以比较这些列,看看它们是否超过某个值,它们必须是单独的爪。

  • 当狗很小或走得更快时,问题会变得更严重。发生的情况是前爪的脚趾仍在接触,而后爪的脚趾刚刚开始接触与前爪相同的区域!

    使用我的简单脚本,它将无法拆分这两个,因为它必须确定该区域的哪些帧属于哪个paw,而目前我只需要查看所有帧的最大值。

开始出错的示例:

alt textalt text

所以现在我正在寻找一种更好的方法来识别和分离爪(之后我将讨论决定哪只爪的问题!).

更新:

我一直在修补乔的(太棒了!)答案实现了,但是我很难从我的文件中提取实际的PAW数据。

alt text

编码后的爪显示了所有不同的爪,当应用到最大压力图像(见上文)。但是,该解决方案将遍历每个帧(以分离重叠爪),并设置四个矩形属性,例如坐标或高度/宽度。

我不知道如何获取这些属性并将它们存储在我可以应用于测量数据的某个变量中。因为我需要知道每个爪的位置,在哪个帧中它的位置是什么,并将其连接到哪个爪上(前/后,左/右)。

那么,如何使用矩形属性为每个爪提取这些值呢?

我的公共Dropbox文件夹中有我在问题设置中使用的度量(例1、例2、例3)。对于任何感兴趣的人,我还建立了一个博客,让您随时了解最新消息:-)


如果您只是想要(半)连续的区域,那么python中已经有了一个简单的实现:scipy的ndimage.monology模块。这是一个相当常见的图像形态学操作。

基本上,您有5个步骤:

1
2
3
4
5
6
7
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
  • 稍微模糊输入数据,以确保爪具有连续的足迹。(只使用更大的内核(structurekwarg到各种scipy.ndimage.morphology函数)会更有效,但由于某些原因,这并不能很好地工作…)

  • 对数组进行阈值设置,这样就有了一个布尔数组,其中压力超过某个阈值(即thresh = data > value)。

  • 填充任何内部孔,使区域更清洁(filled = sp.ndimage.morphology.binary_fill_holes(thresh))

  • 找到单独的相邻区域(coded_paws, num_paws = sp.ndimage.label(filled))。这将返回一个区域按数字编码的数组(每个区域都是一个唯一整数的连续区域(1到paw的数目),其他区域都为零)。

  • 使用data_slices = sp.ndimage.find_objects(coded_paws)隔离相邻区域。这将返回slice对象的元组列表,这样您就可以使用[data[x] for x in data_slices]获得每个paw的数据区域。相反,我们将基于这些切片绘制一个矩形,这需要稍微多做一些工作。

  • 下面的两个动画显示了"重叠爪"和"分组爪"示例数据。这种方法似乎工作得很好。(不管它值多少钱,它比我机器下面的GIF图像运行得更平稳,所以爪检测算法相当快…)

    Overlapping PawsGrouped Paws

    下面是一个完整的例子(现在有更详细的解释)。其中绝大多数是读取输入并制作动画。实际的爪形检测只有5行代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    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')

    更新:就确定哪只爪在什么时候与传感器接触而言,最简单的解决方案是只进行相同的分析,但同时使用所有数据。(即,将输入叠加到一个三维数组中,并使用它,而不是单独的时间帧。)因为Scipy的Ndimage函数用于N维数组,所以我们根本不需要修改原始的paw finding函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    # 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 text

    alt text

    alt text


    我不是图像检测专家,我也不认识Python,但我会给它一个打击…

    要检测单个爪,首先应该选择压力大于某个小阈值的所有对象,非常接近于完全没有压力。上面的每个像素/点都应该"标记"。然后,与所有"标记"像素相邻的每个像素都会被标记,这个过程会重复几次。完全相连的质量会形成,所以你有不同的物体。然后,每个"对象"都有一个最小和最大的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.

    那就差不多了。


    注意:我说像素,但这可能是使用像素平均值的区域。优化是另一个问题…

    听起来您需要为每个像素分析一个函数(随时间变化的压力),并确定函数的旋转方向(当它在另一个方向上改变>X时,它被视为一个旋转以抵消错误)。

    如果你知道它在哪一帧旋转,你就会知道在哪一帧压力最大,你就会知道在两个爪之间压力最小。从理论上讲,你会知道两个帧中爪压得最紧,可以计算出这些间隔的平均值。

    after which I'll get to the problem of deciding which paw it is!

    这和以前一样,知道每个爪子何时施加的压力最大,可以帮助你做出决定。