Memory-Error

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
BlackJack

@crea: Deine Beschreibung ist ein bisschen zu allgemein um da etwas konkretes drauf Antworten zu können. Ganz generell müsstest Du halt statt die einzelnen Frames zu lesen und an eine Liste anzhängen, zum Beispiel eine Generatorfunktion schreiben die an stelle des Anhängens den Frame per ``yield`` liefert.

Die Frage ist natürlich auch ob die Weiterverarbeitung die Du machst mit einzelnen Frames auskommt.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@crea:
Die Idee dahinter ist nichts weiter, als die Daten in sequentielle Häppchen zu zerlegen. Die Teilsequenzen sind dann klein genug, um in den Speicher zu passen. Pythons Generatoren sind eine syntaktisch elegante Form der "Häppchenbereitstellung" über eine Art Koroutine.

Ob das in Deinem speziellen Fall möglich ist, hängt davon ab, ob Du alle Daten für alle Bearbeitungsschritte brauchst. Um das einschätzen zu können, ist Deine Problembeschreibung zu allgemein.

Edit: zu langsam ;)
crea
User
Beiträge: 5
Registriert: Dienstag 26. März 2013, 21:30

Im Endeffekt brauche ich alle Daten für die weitere Bearbeitung, ja.

Ich hatte mir vorgestellt, etwas ähnliches in Python realisieren zu können, wie es in Matlab möglich ist: Eine mehrdimensionales Array, Breite x Höhe x Frames, aus welchen ich dann die entsprechenden Intensitätswerte über die Zeit für verschiedene ROIs extrahieren kann.

Also, ich versuch's mal genauer: Ich möchte ein Video einlesen, in irgendeiner Form interessante Regionen auswählen (per Binning oder manuelle Auswahl eines ROI), die Intensitätsänderung an dieser Stelle/diese Stellen über die Zeit auftragen und eine Fourieranalyse (für den Anfang) darauf anwenden.
Wie ich das umsetzen will? Das Video per OpenCV lesen, die Frames in ein Array übergeben (MemoryError), dann meine Werte in dieser Form mit Numpy weiter bearbeiten/auswerten, für den Anfang per Fourier.
Wieso ich das nicht in Matlab mache, wenn es in Matlab doch möglich ist? Es existiert ein Python-Programm eines Kollegen zu einer weiteren Analyse, welche ich auf meine Daten später anwenden will und ich möchte den oben beschriebenen Prozess in Python umsetzen, damit ich beide Teilanalysen am Ende in einem Programm zusammenführen kann.

Meine Python-Recherche hat bisher ergeben, dass es diverse Möglichkeiten zum Image-Processing, also Handling von einzelnen Frames gibt, allerdings habe ich noch keine elegante Lösung zur Bearbeitung eines kompletten Videos gefunden. Eine Option wäre natürlich, mein Video in einzelne Images zu zerlegen, bevor ich ein Python-Programm drauf loslasse, ich fände es aber schöner, wenn ich meine AVIs direkt verarbeiten könnte, ohne getrennte Vorbehandlung. Falls das nicht geht, würde ich das Video durch Python in einzelne pngs oder so zerlegen und speichern lassen, bevor ich dann die einzelnen png bearbeite.
BlackJack

@crea: Ich bin ein bisschen verwirrt, wenn Du einzelne PNGs verarbeiten kannst, dann kannst Du doch auch direkt die Frames aus dem Video verarbeiten und bräuchtest nicht erst PNGs erstellen.

Die Frage von jerch und mir nach dem Zugriff auf *alle* Daten bezieht sich darauf, ob Du quasi *gleichzeitig* den Zugriff auf alle Daten benötigst, nicht ob während der Verarbeitung *insgesamt* alle Daten mal angefasst werden.

Hast Du ein Gefühl für den Speichebedarf, also hat Du Dir den Speicherbedarf für die reinen Pixeldaten eigentlich schon mal ausgerechnet für die Daten, die Du verarbeiten möchtest? Bekommst Du tatsächlich bei der Umwandlung von *einem* Frame einen `MemoryError`‽

Wenn Du die Daten in ein anderes Format überführst, würde ich HDF5, zum Beispiel mit dem PyTables-Modul, empfehlen. Das Format ist für grosse mehrdimensionale Datenmengen gedacht und wird unter anderem von der NASA für Satellitenbilder und ähnlichem verwendet.
crea
User
Beiträge: 5
Registriert: Dienstag 26. März 2013, 21:30

Den Speicherbedarf habe ich noch nicht ausgerechnet. Allerdings macht folgendes Skript bei der Verarbeitung von z.B. 180 Frames keine Probleme, wohl aber bei 1800. Und der Fehler tritt bei der Erstellung des finalen Arrays auf: frames = np.array(frames).
Es spricht nichts dagegen, dass ich den u.g. Prozess zerlege...D.h. ich brauche erst alle Daten nachdem ich o.g. Array erstellt habe, hatte aber gehofft, dass sich das Problem anders lösen lässt.

Code: Alles auswählen

import cv2
import numpy as np


capture = cv2.cv.CaptureFromFile('20120810-hl1-0.12s-10min-sample2of2-250mul-0002test.avi')
frames = []
for i in range(180):
    img = cv2.cv.QueryFrame(capture)
    tmp = cv2.cv.CreateImage(cv2.cv.GetSize(img),8,3)
    cv2.cv.CvtColor(img,tmp,cv2.cv.CV_BGR2RGB)
    frames.append(np.asarray(cv2.cv.GetMat(tmp))) 
frames = np.array(frames)
Sirius3
User
Beiträge: 18335
Registriert: Sonntag 21. Oktober 2012, 17:20

crea hat geschrieben:Den Speicherbedarf habe ich noch nicht ausgerechnet.
Solltest Du aber: 1800-Frames in Standardauflösung machen ungefähr 2GB. Das np.array verdoppelt den Speicherbedarf nochmales, weil alle Frames in ein neues Array kopiert werden müssen. Diverse Operationen auf solchen Matrizen können den benötigten Platz vervielfachen, da Zwischenergebnisse gebraucht werden, usw.
crea
User
Beiträge: 5
Registriert: Dienstag 26. März 2013, 21:30

Ja, das ist mir inzwischen klar. Daher wollte ich versuchen, o.g. code durch einen Generator umzusetzen, allerdings hat sich mir das Generator/yield-Prinzip (noch) nicht ganz ergeben...
Benutzeravatar
snafu
User
Beiträge: 6908
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Generatoren werden meist so implementiert, dass man eine Schleife baut und mit jedem Iterationsschritt ein Element ausgibt. Etwa so:

Code: Alles auswählen

def iter_items():
    for item in items:
        yield item
Sinnvollerweise tut man natürlich vorab irgendwas mit `item` bevor man es ausgibt.

Eine mögliche Nutzung kann dann sein:

Code: Alles auswählen

list(iter_items())
# oder:
" ".join(iter_items())
Dabei wird dann jeweils ein neuer Iterationsschritt begangen und das zurückgegebene Element in einer wie auch immer gearteten Weise verwendet. Es wird also zwischendurch keine Liste mit (sagen wir mal) 2 Mio Items erstellt, sondern es wird wirklich nur schrittweise die Funktion mit den einzelnen Elementen gefüttert, ohne dass diese als Ganzes im Speicher gehalten werden müssten.

`yield` ist halt der Moment, in dem das `item` "ausgespuckt" wird.

Beachte, dass solche Funktionen keine Ausgabe mehr als `return` zurückgeben dürfen. Ein blankes `return` ist möglich, die Rückgabe eines speziellen Wertes mittels `return` jedoch nicht.

Falls du dich fragst, wann die Funktion fertig ist (terminiert): Das tut sie, sobald kein `yield` Statement mehr angegeben ist. Häufig also, sobald die `for`-Schleife komplett durchlaufen wurde.

Falls das Prozedere relativ trivial ist, geht auch eine noch direktere Anwendung. Beispiel:

Code: Alles auswählen

" ".join(item.upper() for item in items)
...würde alles in Großbuchstaben schreiben. Bitte übergib in dem Fall keine Liste bzw List Comprehension, da dies den besagten Vorteil wieder kaputt machen würde. Die gerade gezeigte Form funktioniert bei jeder beliebigen Funktion, solange kein zweites Argument erforderlich ist. Das Ganze wird damit also recht lesbar.

Jedoch scheint es bei dir etwas komplizierter zu sein, sodass ich eine Funktion (ähnlich wie ganz oben gezeigt) empfehlen würde. Man kann zwar vieles auch in der im letzten Snippet gezeigten Form ausdrücken, jedoch landet man dann schnell bei Mehrfach-Verschachtelungen, was der Leserlichkeit des Codes oft nicht unbedingt zuträglich ist. ;)
crea
User
Beiträge: 5
Registriert: Dienstag 26. März 2013, 21:30

Danke für die Erklärung. Dennoch habe ich meine Schwierigkeiten meine Schleife in einen Generator zu übersetzen. "item" in meinem Fall wäre array, das jeweils einem frame entspricht (np.asarray(cv2.cv.GetMat(tmp))), richtig?
Benutzeravatar
snafu
User
Beiträge: 6908
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Dein Code von oben (ungetestet) in "yield-Form" übertragen:

Code: Alles auswählen

import cv
import numpy as np

def get_frame_iter(capture, num_frames):
    for dummy in xrange(num_frames):
        frame = cv.QueryFrame(capture)
        new_img = cv.CreateImage(cv.GetSize(frame), 8, 3)
        cv.CvtColor(frame, new_img, cv.CV_BGR2RGB)
        yield np.array(cv.GetMat(new_img))

filename = '20120810-hl1-0.12s-10min-sample2of2-250mul-0002test.avi'
capture = cv.CaptureFromFile(filename)
frames = np.fromiter(get_frame_iter(capture, 180), np.array)
Obligatorisch sei erwähnt, dass man normalerweise den Code, der jetzt auf Modulebene liegt, besser in eine `main()`-Funktion verlagern sollte. Und die explizite Angabe von `cv2` habe ich weggelassen, da (bei mir zumindest) auch der Direktzugriff auf `cv` funktioniert. Im Übrigen würde ich empfehlen, das `cv`-Interface überhaupt nicht mehr zu benutzen und stattdessen gänzlich auf `cv2` zu setzen. Dies hat unter anderem den Vorteil, dass dir direkt Numpy-Arrays zurückgeliefert werden und du dir folglich die nötige Konvertierung ersparen kannst, um darauf zu arbeiten. Zudem wird es nur eine Frage der Zeit sein bis die Entwicklung der alten `cv`-Schnittstelle eingestellt wird und man dann eh früher oder später wechseln muss.

EDIT: Hier mal eine `cv2`-Version (hoffe mal, es funktioniert so):

Code: Alles auswählen

import cv2
import numpy as np

def get_frame_iter(capture):
    while True:
        success, frame = capture.read()
        if not success:
            break
        yield cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

def get_frames(filename, num_frames=-1):
    capture = cv2.VideoCapture(filename)
    return np.fromiter(get_frame_iter(capture), np.array, num_frames)

filename = '20120810-hl1-0.12s-10min-sample2of2-250mul-0002test.avi'
frames = get_frames(filename, 180)
EDIT2: Ich merke gerade, dass `np.array` als Datentyp in Numpy nicht möglich ist. Dann müsste mal jemand ran, der sich besser mit Numpy auskennt als ich. Ich denke aber, die Idee, die ich vermitteln wollte, ist trotzdem klar.
Antworten