발 감지 기능을 개선하려면 어떻게해야합니까?


198

각 발 에서 발가락찾는 것에 대한 이전의 질문 후에 , 나는 어떻게 다른지를 측정하기 시작했습니다. 불행히도, 나는 앞의 단계 중 하나 인 발을 인식하는 데 문제가 생겼습니다.

내 개념 증명은 기본적으로 시간이 지남에 따라 각 센서의 최대 압력을 취하여! = 0.0을 찾을 때까지 각 행의 합계를 찾기 시작합니다. 그런 다음 열에 대해 동일한 작업을 수행하고 행이 2 이상인 행을 다시 찾은 즉시 다시 0입니다. 최소 및 최대 행 및 열 값을 일부 색인에 저장합니다.

대체 텍스트

그림에서 볼 수 있듯이 이것은 대부분의 경우에 잘 작동합니다. 그러나이 접근 방식에는 많은 단점이 있습니다 (매우 원시적 인 것 이외).

  • 인간은 '중공 발'을 가질 수 있는데, 이는 발자국 자체에 여러 개의 빈 줄이 있음을 의미합니다. 나는 이것이 (큰) 개들에서도 일어날 수 있다고 두려워했기 때문에 발을 자르기 전에 적어도 2 ~ 3 개의 빈 줄을 기다렸습니다.

    이것은 여러 개의 빈 행에 도달하기 전에 다른 열에서 다른 접점을 만든 경우 문제를 일으켜 영역을 확장합니다. 열을 비교하고 특정 값을 초과하는지 확인하면 별도의 발이어야합니다.

  • 개가 매우 작거나 더 빠른 속도로 걷는 경우 문제가 악화됩니다. 앞발의 발가락이 여전히 접촉하고있는 반면, 뒷발의 발가락이 앞발과 같은 영역 내에서 접촉하기 시작합니다!

    내 간단한 스크립트를 사용하면이 두 영역을 분할 할 수 없습니다. 왜냐하면 해당 영역의 어느 프레임이 어느 풋에 속하는지를 결정해야하기 때문에 현재는 모든 프레임에서 최대 값만 봐야합니다.

잘못 시작한 예 :

대체 텍스트 대체 텍스트

이제 발을 인식하고 분리하는 더 좋은 방법을 찾고 있습니다 (그 후에 발이 어느 것인지 결정하는 문제가 발생합니다!).

최신 정보:

Joe의 (굉장한!) 답변을 구현하려고 애 쓰고 있었지만 파일에서 실제 발 데이터를 추출하는 데 어려움을 겪고 있습니다.

대체 텍스트

coded_paws는 최대 압력 이미지에 적용될 때 모든 다른 발을 보여줍니다 (위 참조). 그러나 솔루션은 각 프레임을 넘어서 (겹치는 발을 분리하기 위해) 좌표 또는 높이 / 너비와 같은 4 개의 사각형 속성을 설정합니다.

이러한 속성을 가져 와서 측정 데이터에 적용 할 수있는 변수에 저장하는 방법을 알 수 없습니다. 각 발에 대해 알아야하기 때문에 그 프레임의 위치는 어느 프레임이며 어떤 발에 있는지 (앞 / 뒤, 왼쪽 / 오른쪽).

그러면 Rectangles 특성을 사용하여 각 발에 대해 이러한 값을 추출 할 수 있습니까?

공개 Dropbox 폴더의 질문 설정에서 사용한 측정 값이 있습니다 ( 예 1 , 예 2 , 예 3 ). 관심있는 사람을 위해 나는 또한 당신을 최신 상태로 유지하기 위해 블로그설정했습니다 :-)


유용한 정보를 제한하는 행 / 열 알고리즘에서 벗어나야 할 것 같습니다.
Tamara Wijsman

12
와! 고양이 제어 소프트웨어?
alxx

실제로 @alxx ;-)의 개 데이터입니다. 그러나 예, 그것들을 진단하는 데 사용됩니다!
Ivo Flipse

4
왜? (걱정하지 않는 것이 더 재미있다 ...)
Ben Regenspan

답변:


358

(반) 연속 영역을 원한다면 Python에는 SciPyndimage.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다양한 scipy.ndimage.morphology기능 의 kwarg)을 사용하는 것이 더 효율적 이지만 어떤 이유로 든 제대로 작동하지 않습니다 ...)

  2. 임계 압력이 어떤 임계 값보다 어디에 있는지 (즉 장소의 부울 배열을 가질 수 있도록 배열 thresh = data > value)

  3. 더 깨끗한 영역을 갖도록 내부 구멍을 채 웁니다 ( filled = sp.ndimage.morphology.binary_fill_holes(thresh))

  4. 별도의 인접한 지역을 찾으십시오 ( coded_paws, num_paws = sp.ndimage.label(filled)). 숫자로 코딩 된 영역이있는 배열을 반환합니다 (각 영역은 다른 곳에서는 0이있는 고유 정수의 연속 영역 (1에서 발 수까지)).

  5. 을 사용하여 연속 영역을 분리하십시오 data_slices = sp.ndimage.find_objects(coded_paws). 그러면 slice객체 튜플 목록이 반환 되므로을 사용하여 각 발에 대한 데이터 영역을 얻을 수 [data[x] for x in data_slices]있습니다. 대신 이러한 슬라이스를 기반으로 직사각형을 그리면 약간 더 많은 작업이 필요합니다.


아래의 두 애니메이션은 "오버 랩핑 폰"및 "그룹화 된 폰"예제 데이터를 보여줍니다. 이 방법은 완벽하게 작동하는 것 같습니다. (그리고 가치가있는 것은 내 컴퓨터에서 아래 GIF 이미지보다 훨씬 원활하게 실행되므로 발 감지 알고리즘이 상당히 빠릅니다.)

겹치는 발 그룹화 된 발


다음은 전체 예입니다 (이제 훨씬 더 자세한 설명이 있습니다). 이것의 대부분은 입력을 읽고 애니메이션을 만드는 것입니다. 실제 발 감지는 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')

업데이트 : 어떤 발이 센서와 접촉하고 있는지 확인하는 한 가장 간단한 해결책은 동일한 분석을 수행하지만 모든 데이터를 한 번에 사용하는 것입니다. SciPy의 ndimage 함수는 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')

대체 텍스트


대체 텍스트


대체 텍스트


82
당신이 얼마나 대단한지 설명 할 수조차 없습니다!
Ivo Flipse

1
@Ivo : 예, Joe에게 더 많은 찬사를 보냅니다 :)하지만 새로운 질문을 시작해야합니까, 아니면 @Joe라면 여기에 대답 하시겠습니까? stackoverflow.com/questions/2546780/…
unutbu

2
실제로 .png를 버리고 convert *.png output.gif. 나는이 예제에서는 잘 작동했지만 imagemagick은 내 컴퓨터를 무릎에 가져 왔습니다. 과거에는 svn.effbot.python-hosting.com/pil/Scripts/gifmaker.py 스크립트를 사용 하여 개별 프레임을 저장하지 않고 파이썬에서 애니메이션 GIF를 직접 작성했습니다. 희망이 도움이됩니다! @unutbu가 언급 한 질문에 예를 게시 할 것입니다.
Joe Kington

1
정보 감사합니다, @Joe. 내 문제의 일부는에 사용 bbox_inches='tight'을 게을리 plt.savefig하고 다른 하나는 조바심이었다 :)
unutbu

4
성스러운 소, 나는 이 답변이 얼마나 위대한 지에 대해 와우 라고 말해야 합니다.
andersoj

4

나는 이미지 감지에 대한 전문가가 아니며 파이썬을 모른다.하지만 나는 그것을 망칠 것이다.

개별 발을 감지하려면 먼저 압력이 거의없는 작은 임계 값보다 큰 압력을 가진 모든 것을 선택해야합니다. 이 위에있는 모든 픽셀 / 포인트는 "표시"되어야합니다. 그리고, 모든 "마킹 된"픽셀에 인접한 모든 픽셀이 마킹되고,이 프로세스는 몇 번 반복된다. 완전히 연결된 매스가 형성되므로 고유 한 객체가 있습니다. 그런 다음 각 "개체"에는 최소 및 최대 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.

그렇게해야합니다.


0

참고 : 나는 픽셀이라고 말하지만 이것은 평균 픽셀을 사용하는 영역 일 수 있습니다. 최적화는 또 다른 문제입니다 ...

각 픽셀의 함수 (시간에 따른 압력)를 분석 하고 함수가 회전 하는 위치를 결정 해야하는 것처럼 들립니다 (다른 방향으로> X가 변경되면 카운터 오류로 간주됩니다).

어떤 프레임이 회전하는지 알면 압력이 가장 어려운 프레임을 알고 두 발 사이에서 가장 힘들었던 위치를 알 수 있습니다. 이론적으로, 당신은 발이 가장 세게 눌린 두 프레임을 알고 그 간격의 평균을 계산할 수 있습니다.

그 후 나는 그것이 어느 발인지 결정하는 문제에 도달 할 것입니다!

이것은 각 발이 언제 가장 많은 압력을 가하는지를 알면 결정하는 데 도움이됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.