Python爪印图像检测的实现与优化

假设我有一个传感器区域,上面有小猫在走,那么,如何用Python来识别小猫的爪子印呢?要知道爪子是走动的啊。

方法基本上是根据:随着时间推移得到每个传感器的最大压力,并且会开始寻找每行的总和,直到找到不为0的地方。然后,它对列执行相同的操作,一旦找到多于2行,那么它们再次为零。它将最小和最大行和列值存储到某些索引以便后面可以查找。
alt text
从图中可以看出,这在大多数情况下效果很好。然而,这种方法有很多缺点(除非是非常原始的实现):

  • 人类可以有“空心脚”,这意味着足迹本身有几排空行。同时,这可能发生在(大)狗身上,那么我们需要等待至少2或3个空行,然后切断爪子。
    如果另一个联系人在达到几个空行之前在另一列中进行了另一个联系,从而扩展了该区域,则会产生问题。我想我可以比较列,看看它们是否超过一定值,它们必须是分开的爪子。
  • 当狗很小或走得更高时,问题会变得更糟。发生什么事情是,前爪的脚趾仍然接触,而后爪的脚趾刚刚开始在与前爪相同的区域内接触!
    使用我的简单脚本,它将无法分割这两个,因为它必须确定该区域的哪个帧属于哪个爪子,而目前我只需要查看所有帧上的最大值。

开始出错的例子:
alt text alt text
所以现在我正在寻找一种更好的方式来识别和分离爪子(之后我会得到决定哪个爪子的问题!)。

alt text
coded_pa​​ws显示了所有不同的爪子,当应用于最大压力图像(见上文)时。然而,解决方案遍历每个框架(以分离重叠的爪子)并设置四个Rectangle属性,如坐标或高度/宽度。
我不知道如何把这些属性存储在一些可以应用于测量数据的变量中。因为我需要知道每个爪子,它的位置是什么,在哪个框架和夫妇这是哪个爪子(前/后,左/右)。
那么如何使用矩形属性来提取每个爪子的这些值?
在我的公共Dropbox文件夹(example 1example 2example 3)中的问题设置中使用了测量。

好了,问题描述清楚了,我们来看看怎么优化这个问题吧。

如果你只是想要(半)连续的区域,那么在Python中已经有一个简单的实现:SciPyndimage.morphology模块。这是一个相当普遍的image morphology操作。
基本上你有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. 将输入数据模糊一点,以确保爪子具有连续的占位面积。 (使用更大的内核(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]获取每个爪子的数据区域。相反,我们将绘制一个基于这些切片的矩形,这需要更多的工作。

下面的两个动画显示您的“重叠爪子”和“分组爪子”示例数据。这种方法似乎是完美的。 (无论什么值得,这运行比我的机器下面的GIF图像顺利,所以爪检测算法是相当快…)
Overlapping Paws 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的ndimage功能是为了使用n维数组,所以我们不必修改原来的爪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')

alt text
alt text
alt text

 

By Code问答

OpenCV-Python实现简单数字识别OCR

我们知道,OpenCV是Python中用来处理图像识别的一个很强大的库。问题从简单的入手,在OpenCV-Python(cv2)中实现“数字识别OCR”。非产品级别的解决方案,主要为了学习。我们希望在OpenCV中学习KNearest和SVM功能。

假设我们有每个数字的100个样本(即图像)。并一起训练OpenCV示例附带的示例letter_recog.py。我们着手弄清楚下列问题:
1)什么是letter_recognition.data文件?如何从自己的数据集构建该文件?
2)results.reval()表示什么?
3)如何使用letter_recognition.data文件(KNearest或SVM)编写一个简单的数字识别工具?

解决方法如下:

想要的是在OpenCV中使用KNearest或SVM功能来实现一个简单的OCR。办法应该如下 (只是为了学习如何使用KNearest进行简单的OCR目的):
1)我的第一个问题是关于openCV示例附带的letter_recognition.data文件。我想知道那个文件里面是什么。
它包含一封信,以及该信的16个特征。
this SOF帮助我找到它。这些16个功能在文档Letter Recognition Using Holland-Style Adaptive Classifiers中进行了说明。
(虽然我不了解一些结尾的功能)
2)由于我知道,在不了解所有这些功能的情况下,很难做到这一点。我尝试了一些其他的论文,但对初学者来说,这些都是有点困难的。
So I just decided to take all the pixel values as my features.(我并不担心准确性或表现,我只是想让它工作,至少在最不准确的情况下)
我拍下了我的训练资料:
enter image description here
(我知道培训数据少了,但是由于所有的字母和字体大小相同,所以我决定尝试这样做)。
为了准备训练数据,我在OpenCV中编写了一个小代码。它做以下事情:
A)它加载图像。
B)选择数字(显然通过轮廓查找和对字母的面积和高度应用约束以避免错误检测)。
C)绘制围绕一个字母的边界矩形,并等待key press manually。这一次我们自己按数字键对应的字母在框中。
D)按下相应的数字键后,将此框重新调整为10×10,并将数组中的100个像素值(这里为样本)和相应的手动输入的数位保存在另一个数组中(这里为响应)。
E)然后将这两个数组保存在单独的txt文件中。
在数字手动分类结束时,列车数据(train.png)中的所有数字都由我们自己手动标记,图像如下图所示:
enter image description here
以下是我用于上述目的的代码(当然不是那么干净):

import sys

import numpy as np
import cv2

im = cv2.imread('pitrain.png')
im3 = im.copy()

gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray,(5,5),0)
thresh = cv2.adaptiveThreshold(blur,255,1,1,11,2)

#################      Now finding Contours         ###################

contours,hierarchy = cv2.findContours(thresh,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)

samples =  np.empty((0,100))
responses = []
keys = [i for i in range(48,58)]

for cnt in contours:
    if cv2.contourArea(cnt)>50:
        [x,y,w,h] = cv2.boundingRect(cnt)

        if  h>28:
            cv2.rectangle(im,(x,y),(x+w,y+h),(0,0,255),2)
            roi = thresh[y:y+h,x:x+w]
            roismall = cv2.resize(roi,(10,10))
            cv2.imshow('norm',im)
            key = cv2.waitKey(0)

            if key == 27:  # (escape to quit)
                sys.exit()
            elif key in keys:
                responses.append(int(chr(key)))
                sample = roismall.reshape((1,100))
                samples = np.append(samples,sample,0)

responses = np.array(responses,np.float32)
responses = responses.reshape((responses.size,1))
print "training complete"

np.savetxt('generalsamples.data',samples)
np.savetxt('generalresponses.data',responses)

现在我们进入培训和测试部分。
对于我测试的部分,我使用下面的图像,它有相同类型的字母,我用来训练。
enter image description here
对于培训,我们做如下
A)加载我们之前已经保存的txt文件
B)创建一个我们正在使用的分类器的实例(这里是KNearest)
C)然后我们使用KNearest.train函数来训练数据
为了测试目的,我们做如下:
A)我们加载用于测试的图像
B)如前所述处理图像,并使用轮廓方法提取每个数字
C)为其绘制边框,然后调整为10×10,并将其像素值存储在数组中,如前所述。
D)然后我们使用KNearest.find_nearest()函数来找到我们给出的最接近的项目。 (如果幸运,它会识别正确的数字。)
我在下面的单一代码中包括了最后两个步骤(培训和测试):

import cv2
import numpy as np

#######   training part    ############### 
samples = np.loadtxt('generalsamples.data',np.float32)
responses = np.loadtxt('generalresponses.data',np.float32)
responses = responses.reshape((responses.size,1))

model = cv2.KNearest()
model.train(samples,responses)

############################# testing part  #########################

im = cv2.imread('pi.png')
out = np.zeros(im.shape,np.uint8)
gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
thresh = cv2.adaptiveThreshold(gray,255,1,1,11,2)

contours,hierarchy = cv2.findContours(thresh,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)

for cnt in contours:
    if cv2.contourArea(cnt)>50:
        [x,y,w,h] = cv2.boundingRect(cnt)
        if  h>28:
            cv2.rectangle(im,(x,y),(x+w,y+h),(0,255,0),2)
            roi = thresh[y:y+h,x:x+w]
            roismall = cv2.resize(roi,(10,10))
            roismall = roismall.reshape((1,100))
            roismall = np.float32(roismall)
            retval, results, neigh_resp, dists = model.find_nearest(roismall, k = 1)
            string = str(int((results[0][0])))
            cv2.putText(out,string,(x,y+h),0,1,(0,255,0))

cv2.imshow('im',im)
cv2.imshow('out',out)
cv2.waitKey(0)

下面是我得到的结果:
enter image description here
这里以100%的精度工作。我假设这是因为所有的数字都是相同的和相同的大小。
但是无论如何,这对初学者来说是一个很好的开始。

 

希望有用。

By Codewenda.com