How can I improve my paw detection?
在我之前关于在每个爪子内找到脚趾的问题之后,我开始加载其他测量值,以查看它如何支撑。不幸的是,我很快就遇到了前面其中一个步骤的问题:识别爪子。
你看,我的概念证明基本上是用每一个传感器的最大压力随着时间的推移,然后开始寻找每一行的总和,直到它找到为止!= 0。然后,它对列执行相同的操作,一旦发现超过2行的数据再次为零。它将最小值和最大值的行和列存储到某个索引中。
正如您在图中看到的,在大多数情况下,这都非常有效。但是,这种方法有很多缺点(不是非常原始):
人类可以有"空心的脚",这意味着在脚印本身有几个空行。因为我担心这种情况也会发生在(大)狗身上,所以我至少等了2到3排空狗才切断了爪子。
如果另一个联系人在到达多个空行之前在不同的列中进行了接触,则会产生问题,从而扩展该区域。我想我可以比较这些列,看看它们是否超过某个值,它们必须是单独的爪。
当狗很小或走得更快时,问题会变得更严重。发生的情况是前爪的脚趾仍在接触,而后爪的脚趾刚刚开始接触与前爪相同的区域!
使用我的简单脚本,它将无法拆分这两个,因为它必须确定该区域的哪些帧属于哪个paw,而目前我只需要查看所有帧的最大值。
开始出错的示例:
所以现在我正在寻找一种更好的方法来识别和分离爪(之后我将讨论决定哪只爪的问题!).
更新:
我一直在修补乔的(太棒了!)答案实现了,但是我很难从我的文件中提取实际的PAW数据。
编码后的爪显示了所有不同的爪,当应用到最大压力图像(见上文)。但是,该解决方案将遍历每个帧(以分离重叠爪),并设置四个矩形属性,例如坐标或高度/宽度。
我不知道如何获取这些属性并将它们存储在我可以应用于测量数据的某个变量中。因为我需要知道每个爪的位置,在哪个帧中它的位置是什么,并将其连接到哪个爪上(前/后,左/右)。
那么,如何使用矩形属性为每个爪提取这些值呢?
我的公共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 |
稍微模糊输入数据,以确保爪具有连续的足迹。(只使用更大的内核(
对数组进行阈值设置,这样就有了一个布尔数组,其中压力超过某个阈值(即
填充任何内部孔,使区域更清洁(
找到单独的相邻区域(
使用
下面的两个动画显示了"重叠爪"和"分组爪"示例数据。这种方法似乎工作得很好。(不管它值多少钱,它比我机器下面的GIF图像运行得更平稳,所以爪检测算法相当快…)
下面是一个完整的例子(现在有更详细的解释)。其中绝大多数是读取输入并制作动画。实际的爪形检测只有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') |
我不是图像检测专家,我也不认识Python,但我会给它一个打击…
要检测单个爪,首先应该选择压力大于某个小阈值的所有对象,非常接近于完全没有压力。上面的每个像素/点都应该"标记"。然后,与所有"标记"像素相邻的每个像素都会被标记,这个过程会重复几次。完全相连的质量会形成,所以你有不同的物体。然后,每个"对象"都有一个最小和最大的x和y值,因此边界框可以整齐地围绕在它们周围。
Pseudocode:
那就差不多了。
注意:我说像素,但这可能是使用像素平均值的区域。优化是另一个问题…
听起来您需要为每个像素分析一个函数(随时间变化的压力),并确定函数的旋转方向(当它在另一个方向上改变>X时,它被视为一个旋转以抵消错误)。
如果你知道它在哪一帧旋转,你就会知道在哪一帧压力最大,你就会知道在两个爪之间压力最小。从理论上讲,你会知道两个帧中爪压得最紧,可以计算出这些间隔的平均值。
after which I'll get to the problem of deciding which paw it is!
这和以前一样,知道每个爪子何时施加的压力最大,可以帮助你做出决定。