如何提高我的爪子探测能力?

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

你看,我的概念证明基本上是随着时间的推移,每个传感器的最大压力,并开始寻找每一行的和,直到它找到!= 0.0。然后它对列也做同样的操作只要它找到超过2行的都是0。它将最小和最大的行值和列值存储到某个索引中。

alt text

正如您在图中所看到的,这在大多数情况下工作得很好。然而,这种方法有很多缺点(除了非常原始之外):

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

    这就产生了一个问题,如果在到达几个空行之前在不同的列中进行了另一个接触,从而扩大了区域。我想我可以比较这些列,看看它们是否超过了某个值,它们必须是独立的paws

  • 当狗很小或走得更快时,问题会变得更严重。事实是,前爪的脚趾仍然在接触,而后爪的脚趾刚刚开始接触前爪的同一区域!

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

开始出错的地方:

alt text alt text

所以现在我在寻找一种更好的方法来识别和分离爪子(之后我将得到的问题,决定哪一个爪子!)。

更新:

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

alt text

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

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

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

我在我的Dropbox公共文件夹(示例1示例2示例3)中有我在问题设置中使用的测量值。对于感兴趣的人,我也建立了一个博客让你保持最新:-)

15250 次浏览

我不是图像检测方面的专家,也不懂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.

这样就差不多了。

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

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

如果你知道它在哪个帧转弯,你就会知道哪个帧的压力最大,你就会知道两只爪子之间哪个帧的压力最小。从理论上讲,你就会知道爪子最用力按压的两帧,并可以计算出这些间隔的平均值。

然后我再来决定是哪只爪子的问题!

这和之前的旅行是一样的,知道每只爪子在什么时候施加最大的压力可以帮助你决定。

如果你只是想要(半)连续的区域,在Python中已经有一个简单的实现:SciPyndimage.morphology模块。这是一个相当常见的图像形态学操作。


基本上,你有五个步骤:

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. 模糊一点输入数据,以确保爪子有一个连续的足迹。(使用更大的内核(structure kwarg到各种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]获取每个paw的数据区域。相反,我们将基于这些切片绘制一个矩形,这需要稍微多一点工作。


下面的两个动画展示了“重叠爪子”和“分组爪子”的示例数据。这个方法似乎很有效。(不管怎么说,在我的机器上,这比下面的GIF图像运行得流畅多了,所以爪子检测算法相当快……)

重叠的爪子 Grouped Paws

.

下面是一个完整的例子(现在有了更详细的解释)。这其中绝大部分是读取输入并制作动画。实际的爪子检测只有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')

更新:至于识别哪个爪子在什么时间与传感器接触,最简单的解决方案是只做同样的分析,但一次使用所有的数据。(即将输入叠加到一个3D数组中,并使用它,而不是单独的时间框架。)因为SciPy的nimage函数是用来处理n维数组的,所以我们根本不需要修改原来的寻爪函数。

# 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