Jak wykryć ruch między dwoma obrazami PIL? (dołączony przykład integracji kamery internetowej wxPython)

Czy ktoś ma jakieś sugestie, jak mogę porównać obraz w Pythonie, aby wykryć zmiany w obrazie? Obecnie pracuję nad aplikacją, która będzie monitorować mój obszar za pomocą mojej kamery internetowej, chciałbym dowiedzieć się, jak porównać obrazy wykonane każdą klatkę, aby zobaczyć, czy wykryto jakikolwiek ruch. Na dłuższą metę chciałbym ustawić suwak czułości, więc jeśli jesteś w stanie poprowadzić mnie w kierunku, jestem pewien, że mogę dowiedzieć się resztę.

Jak widziałem kilka postów na tutaj Pytanie o integrację kamery internetowej z wxPython, oto małe demo. Zauważ, że właśnie zacząłem go wczoraj wieczorem, więc jeśli szukasz tip top code, być może będziesz musiał sam go poprawić (na razie;): {]}

Wymagania: PIL & VideoCapture

#videocapturepanel.py

#Todo:
# - Fix background colour after video is stopped
# - Create image comparison method
# - Add capture function
# - Save stream to video file?


import threading, wx
from PIL          import Image
from VideoCapture import Device

cam = Device(0)
buffer, width, height = cam.getBuffer()
cam.setResolution(width, height)

DEFAULT_DEVICE_INDEX  = 0
DEFAULT_DEVICE_WIDTH  = width
DEFAULT_DEVICE_HEIGHT = height
DEFAULT_BACKGROUND_COLOUR = wx.Colour(0, 0, 0)

class VideoCaptureThread(threading.Thread):

    def __init__(self, control, width=DEFAULT_DEVICE_WIDTH, height=DEFAULT_DEVICE_HEIGHT, backColour=DEFAULT_BACKGROUND_COLOUR):
        self.backColour = backColour
        self.width      = width
        self.height     = height
        self.control    = control
        self.isRunning  = True
        self.buffer     = wx.NullBitmap

        threading.Thread.__init__(self)

    def getResolution(self):
        return (self.width, self.height)

    def setResolution(self, width, height):
        self.width  = width
        self.height = height
        cam.setResolution(width, height)

    def getBackgroundColour(self):
        return self.backColour

    def setBackgroundColour(self, colour):
        self.backColour = colour

    def getBuffer(self):
        return self.buffer

    def stop(self):
        self.isRunning = False

    def run(self):
        while self.isRunning:
            buffer, width, height = cam.getBuffer()
            im = Image.fromstring('RGB', (width, height), buffer, 'raw', 'BGR', 0, -1)
            buff = im.tostring()
            self.buffer = wx.BitmapFromBuffer(width, height, buff)
            x, y = (0, 0)
            try:
                width, height = self.control.GetSize()
                if width > self.width:
                    x = (width - self.width) / 2
                if height > self.height:
                    y = (height - self.height) / 2
                dc = wx.BufferedDC(wx.ClientDC(self.control), wx.NullBitmap, wx.BUFFER_VIRTUAL_AREA)
                dc.SetBackground(wx.Brush(self.backColour))
                dc.Clear()
                dc.DrawBitmap(self.buffer, x, y)
            except TypeError:
                pass
            except wx.PyDeadObjectError:
                pass
        self.isRunning = False


class VideoCapturePanel(wx.Panel):

    def __init__(self, parent, id=-1, pos=wx.DefaultPosition, size=wx.DefaultSize, initVideo=False, style=wx.SUNKEN_BORDER):
        wx.Panel.__init__(self, parent, id, pos, size, style)

        if initVideo:
            self.StartVideo()

        self.Bind(wx.EVT_CLOSE, self.OnClose)

    def OnClose(self, event):
        try:
            self.Device.stop()
        except:
            pass

    def StopVideo(self):
        self.Device.stop()
        self.SetBackgroundColour(self.Device.backColour)
        dc = wx.BufferedDC(wx.ClientDC(self), wx.NullBitmap)
        dc.SetBackground(wx.Brush(self.Device.backColour))
        dc.Clear()

    def StartVideo(self):
        self.Device = VideoCaptureThread(self)
        self.Device.start()

    def GetBackgroundColour(self):
        return self.Device.getBackgroundColour()

    def SetBackgroundColour(self, colour):
        self.Device.setBackgroundColour(colour)


class Frame(wx.Frame):

    def __init__(self, parent, id=-1, title="A Frame", path="", pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE):
        wx.Frame.__init__(self, parent, id, title, pos, size, style)
        self.VidPanel = VideoCapturePanel(self, -1, initVideo=False)
        self.StartButton  = wx.ToggleButton(self, -1, "Turn On")
        self.ColourButton = wx.Button(self, -1, "Change Background")
        szr  = wx.BoxSizer(wx.VERTICAL)
        bszr = wx.BoxSizer(wx.HORIZONTAL)
        bszr.Add(self.StartButton, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.LEFT, 5)
        bszr.Add(self.ColourButton, 0, wx.ALIGN_CENTER_HORIZONTAL)
        szr.Add(self.VidPanel, 1, wx.EXPAND)
        szr.Add(bszr, 0, wx.ALIGN_CENTER_HORIZONTAL)
        self.SetSizer(szr)

        self.StartButton.Bind(wx.EVT_TOGGLEBUTTON, self.OnToggled)
        self.ColourButton.Bind(wx.EVT_BUTTON, self.OnColour)


    def OnColour(self, event):
        dlg = wx.ColourDialog(self)
        dlg.GetColourData().SetChooseFull(True)

        if dlg.ShowModal() == wx.ID_OK:
            data = dlg.GetColourData()
            self.VidPanel.SetBackgroundColour(data.GetColour())
        dlg.Destroy()


    def OnToggled(self, event):
        if event.IsChecked():
            self.VidPanel.StartVideo()
        else:
            self.VidPanel.StopVideo()
            #self.VidPanel.SetBackgroundColour(data.GetColour())


if __name__ == "__main__":
    # Run GUI
    app   = wx.PySimpleApp()
    frame = Frame(None, -1, "Test Frame", size=(800, 600))
    frame.Show()
    app.MainLoop()
    del app

*Aktualizacja*

Na przykładzie Paula stworzyłem klasę i zaimplementowałem ją do kodu:

class Images:

    def __init__(self, image1, image2, threshold=98, grayscale=True):
        self.image1 = image1
        if type(image1) == str:
            self.image1 = Image.open(self.image1)
        self.image2 = image2
        if type(image2) == str:
            self.image2 = Image.open(image2)
        self.threshold = threshold

    def DoComparison(self, image1=None, image2=None):
        if not image1: image1 = self.image1
        if not image2: image2 = self.image2
        diffs = ImageChops.difference(image1, image2)
        return self.ImageEntropy(diffs)

    def ImageEntropy(self, image):
        histogram   = image.histogram()
        histlength  = sum(histogram)
        probability = [float(h) / histlength for h in histogram]
        return -sum([p * math.log(p, 2) for p in probability if p != 0])

A następnie dodano zmienną self.image = False to VideoCaptureThread ' S __init__() funkcja, i dodano poniższy kod do funkcji videocaptrethread run() po linii im = Image.fromstring(...):

        if self.image:
            img = compare.Images2(im, self.image).DoComparison()
            print img
        self.image = im

Kiedy uruchamiam próbkę, wydaje się działać ok, ale jestem trochę zdezorientowany z wynikami, które otrzymuję:

1.58496250072
5.44792407663
1.58496250072
5.44302784225
1.58496250072
5.59144486002
1.58496250072
5.37568050189
1.58496250072

Jak na razie wygląda na to, że co drugi obrazek jest dość wyłączony, chociaż zmiany są minimalne? Dodatek do run powinien teoretycznie uchwycić poprzedni obrazek pod zmienną siebie.obraz i porównaj z nowym obrazem im. Po porównaniu, siebie.obraz jest aktualizowany do bieżącego obrazu za pomocą siebie.image = im, skąd więc taka różnica w każdym drugim obrazie? Co najwyżej Moje oczy mogły przesunąć się w przód / w tył w ramach dwóch obrazów, a ja nie widzę, że powodując taką różnicę z moimi wynikami?

*Aktualizacja 2*

Oto co mam do tej pory, są trzy klasy porównawcze z trzema różnymi metodami wykrywania ruchu.

zdjęcia klasowe ~ pierwsza próba użycia kodu, który znalazłem podczas googlowania, nawet nie pamiętam, jak to działa tbh. : P

class Images2 ~ stworzony przy użyciu kodu Paula z tego wątku, implementującego jego zaktualizowaną funkcję entropii obrazu.

klasa Images3 ~ zmodyfikowana wersja funkcji DetectMotion została znaleziona tutaj . (Zwraca procent zmieniony i wydaje się, że bierze pod uwagę oświetlenie)

Prawdę mówiąc, naprawdę nie mam pojęcia, co któreś z nich robi, dosłownie, ale mogę powiedzieć, że do tej pory class Image3 wydaje się być najprostszym/dokładnym sposobem ustawienia detekcji, upadek jest to, że zajmuje więcej czasu, aby przetworzyć niż dwie pozostałe klasy.

(Należy pamiętać, że niektóre zmiany importu zostały wprowadzone do unikaj kolizji z scipy, sys.modules ["Image"] jest taki sam jak PIL.Image)

import math, sys, numpy as np
import PIL.Image, PIL.ImageChops

sys.modules["Image"]      = PIL.Image
sys.modules["ImageChops"] = PIL.ImageChops

from scipy.misc   import imread
from scipy.linalg import norm
from scipy        import sum, average


DEFAULT_DEVICE_WIDTH  = 640
DEFAULT_DEVICE_HEIGHT = 480


class Images:

    def __init__(self, image1, image2, threshold=98, grayscale=True):
        if type(image1) == str:
            self.image1 = sys.modules["Image"].open(image1)
            self.image2 = sys.modules["Image"].open(image2)
        if grayscale:
            self.image1 = self.DoGrayscale(imread(image1).astype(float))
            self.image2 = self.DoGrayscale(imread(image2).astype(float))
        else:
            self.image1    = imread(image1).astype(float)
            self.image2    = imread(image2).astype(float)
        self.threshold = threshold

    def DoComparison(self, image1=None, image2=None):
        if image1: image1 = self.Normalize(image1)
        else:      image1 = self.Normalize(self.image1)
        if image2: image2 = self.Normalize(image2)
        else:      image2 = self.Normalize(self.image2)
        diff = image1 - image2
        m_norm = sum(abs(diff))
        z_norm = norm(diff.ravel(), 0)
        return (m_norm, z_norm)

    def DoGrayscale(self, arr):
        if len(arr.shape) == 3:
            return average(arr, -1)
        else:
            return arr

    def Normalize(self, arr):
        rng = arr.max()-arr.min()
        amin = arr.min()
        return (arr-amin)*255/rng


class Images2:

    def __init__(self, image1, image2, threshold=98, grayscale=True):
        self.image1 = image1
        if type(image1) == str:
            self.image1 = sys.modules["Image"].open(self.image1)
        self.image2 = image2
        if type(image2) == str:
            self.image2 = sys.modules["Image"].open(image2)
        self.threshold = threshold

    def DoComparison(self, image1=None, image2=None):
        if not image1: image1 = self.image1
        if not image2: image2 = self.image2
        diffs = sys.modules["ImageChops"].difference(image1, image2)
        return self.ImageEntropy(diffs)

    def ImageEntropy(self, image):
        w,h = image.size
        a = np.array(image.convert('RGB')).reshape((w*h,3))
        h,e = np.histogramdd(a, bins=(16,)*3, range=((0,256),)*3)
        prob = h/np.sum(h)
        return -np.sum(np.log2(prob[prob>0]))


    def OldImageEntropy(self, image):
        histogram   = image.histogram()
        histlength  = sum(histogram)
        probability = [float(h) / histlength for h in histogram]
        return -sum([p * math.log(p, 2) for p in probability if p != 0])



class Images3:

    def __init__(self, image1, image2, threshold=8):
        self.image1 = image1
        if type(image1) == str:
            self.image1 = sys.modules["Image"].open(self.image1)
        self.image2 = image2
        if type(image2) == str:
            self.image2 = sys.modules["Image"].open(image2)
        self.threshold = threshold

    def DoComparison(self, image1=None, image2=None):
        if not image1: image1 = self.image1
        if not image2: image2 = self.image2
        image = image1
        monoimage1 = image1.convert("P", palette=sys.modules["Image"].ADAPTIVE, colors=2)
        monoimage2 = image2.convert("P", palette=sys.modules["Image"].ADAPTIVE, colors=2)
        imgdata1   = monoimage1.getdata()
        imgdata2   = monoimage2.getdata()

        changed = 0
        i = 0
        acc = 3

        while i < DEFAULT_DEVICE_WIDTH * DEFAULT_DEVICE_HEIGHT:
            now  = imgdata1[i]
            prev = imgdata2[i]
            if now != prev:
                x = (i % DEFAULT_DEVICE_WIDTH)
                y = (i / DEFAULT_DEVICE_HEIGHT)
                try:
                    #if self.view == "normal":
                    image.putpixel((x,y), (0,0,256))
                    #else:
                    #    monoimage.putpixel((x,y), (0,0,256))
                except:
                    pass
                changed += 1
            i += 1
        percchange = float(changed) / float(DEFAULT_DEVICE_WIDTH * DEFAULT_DEVICE_HEIGHT)
        return percchange


if __name__ == "__main__":
    # image1 & image2 MUST be legit paths!
    image1 = "C:\\Path\\To\\Your\\First\\Image.jpg"
    image2 = "C:\\Path\\To\\Your\\Second\\Image.jpg"

    print "Images Result:"
    print Images(image1, image2).DoComparison()

    print "\nImages2 Result:"
    print Images2(image1, image2).DoComparison()

    print "\nImages3 Result:"
    print Images3(image1, image2).DoComparison()
Author: AWainb, 2011-04-02

1 answers

To może być naiwne podejście, ale to proste miejsce do rozpoczęcia. Jestem pewien, że będziesz mieć wpływ na szum kamery i możesz chcieć odróżnić zmiany w oświetleniu od zmian w kompozycji obrazu. Ale oto co przyszło mi do głowy:

Możesz użyć Pil ImageChops , aby skutecznie odróżnić obrazy. Następnie możesz wziąć entropię tego różnicy, aby uzyskać próg pojedynczej wartości.

Wydaje się działać:

from PIL import Image, ImageChops
import math

def image_entropy(img):
    """calculate the entropy of an image"""
    # this could be made more efficient using numpy
    histogram = img.histogram()
    histogram_length = sum(histogram)
    samples_probability = [float(h) / histogram_length for h in histogram]
    return -sum([p * math.log(p, 2) for p in samples_probability if p != 0])

# testing..

img1 = Image.open('SnowCam_main1.jpg')
img2 = Image.open('SnowCam_main2.jpg')
img3 = Image.open('SnowCam_main3.jpg')

# No Difference
img = ImageChops.difference(img1,img1)
img.save('test_diff1.png')
print image_entropy(img) # 1.58496250072

# Small Difference
img = ImageChops.difference(img1,img2)
img.save('test_diff2.png') 
print image_entropy(img) # 5.76452986917

# Large Difference
img = ImageChops.difference(img1,img3)
img.save('test_diff3.png')
print image_entropy(img) # 8.15698432026

To, jak sądzę, jest znacznie lepszy algorytm dla entropii obrazu, ponieważ jest trójwymiarowy w przestrzeni kolorów, a nie tworzy osobny histogram dla każdego pasma.

edycja-ta funkcja została zmieniona 6-Apr-2012

import numpy as np
def image_entropy(img):
    w,h = img.size
    a = np.array(img.convert('RGB')).reshape((w*h,3))
    h,e = np.histogramdd(a, bins=(16,)*3, range=((0,256),)*3)
    prob = h/np.sum(h) # normalize
    prob = prob[prob>0] # remove zeros
    return -np.sum(prob*np.log2(prob))

To są moje zdjęcia testowe:

Tutaj wpisz opis obrazka

Zdjęcie 1

Tutaj wpisz opis obrazka

Obrazek 2

Tutaj wpisz opis obrazka

Image 3

 10
Author: Paul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:53:31