ฉันจะปรับปรุงการตรวจจับอุ้งเท้าได้อย่างไร


198

หลังจากคำถามก่อนหน้าของฉันเกี่ยวกับการค้นหานิ้วเท้าภายในอุ้งเท้าแต่ละฉันเริ่มโหลดการวัดอื่น ๆ เพื่อดูว่ามันจะถือ น่าเสียดายที่ฉันพบปัญหาอย่างรวดเร็วด้วยหนึ่งในขั้นตอนก่อนหน้านี้: การจดจำอุ้งเท้า

คุณจะเห็นว่าการพิสูจน์แนวคิดของฉันนั้นใช้แรงกดดันสูงสุดของเซ็นเซอร์แต่ละตัวเมื่อเวลาผ่านไปและจะเริ่มมองหาผลรวมของแต่ละแถวจนกระทั่งพบว่าเป็นเช่นนั้น! = 0.0 จากนั้นจะทำเช่นเดียวกันสำหรับคอลัมน์และทันทีที่พบแถวมากกว่า 2 แถวโดยที่เป็นศูนย์อีกครั้ง มันเก็บค่าแถวและคอลัมน์ที่น้อยที่สุดและสูงสุดในดัชนีบางอย่าง

ข้อความแสดงแทน

อย่างที่คุณเห็นในรูปนี้มันใช้งานได้ดีในกรณีส่วนใหญ่ อย่างไรก็ตามมีข้อเสียมากมายสำหรับวิธีนี้ (นอกเหนือจากการใช้วิธีดั้งเดิมมาก):

  • มนุษย์สามารถมี 'เท้ากลวง' ซึ่งหมายความว่ามีหลายแถวที่ว่างเปล่าภายในรอยเท้า เนื่องจากฉันกลัวว่าสิ่งนี้อาจเกิดขึ้นกับสุนัข (ใหญ่) ด้วยฉันรออย่างน้อย 2 หรือ 3 แถวก่อนตัดอุ้งมือ

    สิ่งนี้จะสร้างปัญหาหากผู้ติดต่อรายอื่นในคอลัมน์ที่แตกต่างกันก่อนที่จะถึงแถวที่ว่างเปล่าจำนวนมากดังนั้นจึงเป็นการขยายพื้นที่ ฉันคิดว่าฉันสามารถเปรียบเทียบคอลัมน์และดูว่าพวกเขามีค่าเกินกว่าที่กำหนดหรือไม่พวกเขาจะต้องแยกอุ้งเท้า

  • ปัญหาจะแย่ลงเมื่อสุนัขตัวเล็กมากหรือเดินด้วยความเร็วสูงกว่า เกิดอะไรขึ้นคือเท้าของอุ้งเท้าหน้ายังคงสัมผัสกันอยู่ในขณะที่นิ้วเท้าหลังของอุ้งเท้าเพิ่งเริ่มสัมผัสกันภายในบริเวณเดียวกับอุ้งเท้าหน้า!

    ด้วยสคริปต์ที่เรียบง่ายของฉันมันจะไม่สามารถแยกสองคนนี้ได้เพราะมันจะต้องพิจารณาว่าเฟรมของพื้นที่นั้นเป็นของอุ้งเท้าอันไหนในขณะนี้ฉันต้องดูค่าสูงสุดของเฟรมทั้งหมด

ตัวอย่างของการเริ่มต้นผิดพลาด:

ข้อความแสดงแทน ข้อความแสดงแทน

ดังนั้นตอนนี้ฉันกำลังมองหาวิธีที่ดีกว่าในการจดจำและแยกอุ้งเท้า (หลังจากนั้นฉันจะไปที่ปัญหาของการตัดสินใจว่าอุ้งเท้ามันคืออะไร!)

ปรับปรุง:

ฉันพยายามหาคำตอบของ Joe (ยอดเยี่ยม!) แต่ฉันมีปัญหาในการแยกข้อมูลอุ้งเท้าจริงออกจากไฟล์ของฉัน

ข้อความแสดงแทน

coded_paws แสดงให้ฉันเห็นอุ้งเท้าที่แตกต่างกันทั้งหมดเมื่อนำไปใช้กับภาพความดันสูงสุด (ดูด้านบน) อย่างไรก็ตามการแก้ปัญหาจะผ่านแต่ละเฟรม (เพื่อแยกอุ้งเท้าที่ทับซ้อนกัน) และตั้งค่าคุณลักษณะสี่อย่างเช่นพิกัดหรือความสูง / ความกว้าง

ฉันไม่สามารถหาวิธีนำคุณลักษณะเหล่านี้มาใช้และเก็บไว้ในตัวแปรบางตัวที่ฉันสามารถนำไปใช้กับข้อมูลการวัดได้ เนื่องจากฉันจำเป็นต้องรู้สำหรับอุ้งเท้าแต่ละตัวสิ่งที่ตั้งอยู่ในระหว่างที่เฟรมและคู่นี้ที่อุ้งเท้ามันคือ (ด้านหน้า / หลัง, ซ้าย / ขวา)

ดังนั้นฉันจะใช้แอตทริบิวต์ Rectangles เพื่อแยกค่าเหล่านี้สำหรับอุ้งเท้าแต่ละตัวได้อย่างไร

ฉันมีการวัดที่ฉันใช้ในการตั้งค่าคำถามในโฟลเดอร์สาธารณะ Dropbox ของฉัน ( ตัวอย่างที่ 1 , ตัวอย่างที่ 2 , ตัวอย่างที่ 3 ) สำหรับผู้ที่สนใจฉันยังตั้งค่าบล็อกเพื่อให้คุณทันสมัย ​​:-)


ดูเหมือนว่าคุณจะต้องหันหน้าหนีจากอัลกอริทึมแถว / คอลัมน์คุณจะ จำกัด ข้อมูลที่เป็นประโยชน์
Tamara Wijsman

12
ว้าว! ซอฟต์แวร์ควบคุม Cat?
alxx

เป็นข้อมูลสุนัขจริง @alxx ;-) แต่ใช่มันจะถูกใช้เพื่อวินิจฉัยพวกมัน!
Ivo Flipse

4
ทำไม? (ไม่เป็นไรมันสนุกกว่าที่ไม่รู้ ... )
Ben Regenspan

คำตอบ:


358

หากคุณต้องการพื้นที่กึ่งประชิดมีการใช้งานที่ง่ายใน Python: โมดูลndimage.morphologyของSciPy นี่คือการดำเนินการสัณฐานวิทยาของภาพที่ค่อนข้างธรรมดา


โดยทั่วไปคุณมี 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. เบลอข้อมูลอินพุตเล็กน้อยเพื่อให้แน่ใจว่าอุ้งเท้ามีรอยต่อเนื่อง (มันจะมีประสิทธิภาพมากกว่าเพียงแค่ใช้เคอร์เนลที่มีขนาดใหญ่กว่า (the structurekwarg ไปยัง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)แยกดินแดนที่อยู่ติดกันโดยใช้ ผลตอบแทนนี้รายการ tuples ของวัตถุเพื่อให้คุณสามารถได้รับพื้นที่ของข้อมูลสำหรับแต่ละตีนกับslice [data[x] for x in data_slices]แต่เราจะวาดสี่เหลี่ยมตามชิ้นส่วนเหล่านี้ซึ่งใช้งานได้มากกว่าเดิมเล็กน้อย


ภาพเคลื่อนไหวสองภาพด้านล่างแสดงข้อมูลตัวอย่าง "Overlapping Paws" และ "Paws ที่จัดกลุ่ม" ของคุณ วิธีนี้ดูเหมือนว่าจะทำงานได้อย่างสมบูรณ์ (และสำหรับสิ่งที่คุ้มค่ามันทำงานได้ราบรื่นกว่าภาพ 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')

อัปเดต:เท่าที่ระบุว่าอุ้งเท้าอันใดที่สัมผัสกับเซ็นเซอร์ในเวลาใดโซลูชั่นที่ง่ายที่สุดคือทำการวิเคราะห์แบบเดียวกัน แต่ใช้ข้อมูลทั้งหมดในครั้งเดียว (เช่นสแต็กอินพุตลงในอาร์เรย์ 3D และทำงานกับมันแทนกรอบเวลาของแต่ละบุคคล) เนื่องจากฟังก์ชั่น ndimage ของ SciPy นั้นมีความหมายที่จะทำงานกับอาร์เรย์ n-Dim เราไม่จำเป็นต้องแก้ไขฟังก์ชั่นการค้นหาอุ้งเท้าดั้งเดิม เลย

# 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 ถ้าคุณตอบคำถามที่นี่? stackoverflow.com/questions/2546780/…
unutbu

2
ที่จริงผมเพียงแค่ทิ้งออกของ .png convert *.png output.gifและได้ ฉันเคยจินตนาการเอาไว้ว่าเครื่องของฉันจะคุกเข่ามาก่อนแม้ว่ามันจะใช้ได้ดีสำหรับตัวอย่างนี้ ก่อนหน้านี้ฉันเคยใช้สคริปต์นี้: svn.effbot.python-hosting.com/pil/Scripts/gifmaker.pyเพื่อเขียน gif เคลื่อนไหวโดยตรงจาก python โดยไม่บันทึกแต่ละเฟรม หวังว่าจะช่วย! ฉันจะโพสต์ตัวอย่างที่คำถาม @unutbu พูดถึง
Joe Kington

1
ขอบคุณสำหรับข้อมูล @Joe ส่วนหนึ่งของปัญหาของฉันคือการละเลยที่จะใช้bbox_inches='tight'ในplt.savefig, อื่น ๆ ที่เป็นความอดทน :)
unutbu

4
วัวศักดิ์สิทธิ์ฉันต้องบอกว้าวว่าคำตอบนี้ดีแค่ไหน
andersoj

4

ฉันไม่มีความเชี่ยวชาญในการตรวจจับภาพและฉันไม่รู้จัก 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.

ที่ควรเกี่ยวกับการทำมัน


0

บันทึก:ฉันพูดพิกเซล แต่อาจเป็นภูมิภาคที่ใช้ค่าเฉลี่ยของพิกเซล การเพิ่มประสิทธิภาพเป็นปัญหาอื่น ...

เสียงเหมือนที่คุณต้องการในการวิเคราะห์ฟังก์ชั่น (ความดันเมื่อเวลาผ่านไป) สำหรับแต่ละพิกเซลและกำหนดว่าฟังก์ชั่นจะเปลี่ยนไปที่ใด (เมื่อมันเปลี่ยน> X ในอีกทางหนึ่ง

ถ้าคุณรู้ว่ามันเปลี่ยนเฟรมอะไรคุณจะรู้ว่าเฟรมไหนที่ความดันนั้นหนักที่สุดและคุณจะรู้ว่ามันเป็นจุดที่แข็งที่สุดระหว่างอุ้งเท้าทั้งสอง ในทางทฤษฎีแล้วคุณจะรู้ว่าสองเฟรมที่อุ้งเท้ากดหนักที่สุดและสามารถคำนวณค่าเฉลี่ยของช่วงเวลาเหล่านั้นได้

หลังจากนั้นฉันจะไปที่ปัญหาของการตัดสินใจว่ามันคืออุ้งเท้า!

นี่คือทัวร์เดียวกับเมื่อก่อนรู้ว่าเมื่อใดที่อุ้งเท้าแต่ละตัวมีแรงกดดันมากที่สุดจะช่วยให้คุณตัดสินใจได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.