Seite 1 von 1

Laser graffiti

Verfasst: Sonntag 25. Oktober 2009, 13:45
von Nko
Hallo zusammen,

"laser graffiti" ist die (mir nicht eigene) Idee, die Projektion eines Projektors mit einer Videokamera zu erfassen und darauf erfasste Laserreflexionen in Pinselstriche umzuwandeln (und zu projizieren). So kann man mit einem Laserpointer auf Hauswänden "malen". (Es gibt ein paar schöne Videos bei DuRohr.com)

Eine "Billigversion" in Python mit der Hilfe von "motion":
Das Programm "motion" (http://www.lavrsen.dk/twiki/bin/view/Motion/WebHome) kann in erfassten Bildern Pixeländerungen erkennen. Mit den folgenden Angaben (in ~/motion.conf)

Code: Alles auswählen

# Target base directory for pictures and films
# Recommended to use absolute path. (Default: current working directory)
target_dir /home/....../pics/
# Output pictures with only the pixels moving object (ghost images) (default: off)
output_motion on
# %K and %L = X and Y coordinates of motion center
jpeg_filename motionCaps%H%M%S_%q__%K_%L
kann "motion -c motion.conf" gestartet werden und jpegs mit dem gewünschten Filenamen werden generiert. Die Position des Bewegungszentrums ist im Filenamen enthalten (ist billig, aber motion kann sonst nur über seriell Informationen schicken und Das wirkliche Bild zu parsen soll die nächste Version werden).

Ich habe folgendes zusammengefrickelt, um die Positionen auszulesen und per pygame zu "malen":

Code: Alles auswählen

import pygame
from glob import glob
from os import system 

picturePath="./pics/motionCap*.jpg"

def drawMyCircle(window,center):
    """draws a white circle on 'window' at position 'center' with size 3""" 
    pygame.draw.circle(window, (255, 255, 255),center,3)
    pygame.display.flip()
    
def calcCenterList(filelist):
    """receives positions of movement by parsing all filenames in 'filelist'
    returns a list of positions
    ATTENTION: removes all files in 'filelist' from disc!!!"""    
    centers=[]
    if (filelist!=[]):
        for i in filelist:
            newx=int(i.split("_")[3])
            newy=int(i.split("_")[4].split("m")[0])
            centers.append((newx,newy))
            system("rm "+i+" &")
    return centers

if __name__=="__main__":
    pygame.init() 
    window = pygame.display.set_mode((640, 480)) 
 
    while True: 
        filelist=glob(picturePath)
        centers=calcCenterList(filelist)
        for i in centers:
            drawMyCircle(window,i)
Es funktioniert, leider bringt mir motion nur 8 Bilder pro Sekunde, obwohl er mehr können müsste. Ich glaube, das liegt aber an meiner (internen) USB-webcam. Aber mit acht Punkten pro Sekunde kann man an der Zimmerdecke langsam herummalen.
Vielleicht hat ja einer von Euch Lust, das Programm zu testen.

Obwohl "rm" nur ausgeführt wird, wenn filelist nicht leer ist, meldet das Betriebssystem manchmal: "rm: kann file nicht löschen, da nicht vorhanden". Das verstehe ich nicht, wie das sein kann.
Irgendwelche Kommentare zum Code?

Liebe Grüße, Nko

Verfasst: Sonntag 25. Oktober 2009, 14:22
von BlackJack
@Nko: Als erstes fällt natürlich auf, dass Namensgebung und Leerzeichensetzung nicht PEP8-Konform ist.

Klammern braucht man um Bedingungen nicht und `filelist` auf eine leere Liste zu prüfen ist auch überflüssig. Wenn die Liste leer ist, dann wird die ``for``-Schleife halt nicht durchlaufen, auch ohne dass man das vorher prüft.

`i` in einer Schleife an etwas anderes als ganze Zahlen zu binden ist keine gute Idee. Das erwarten Programmierer nicht. Und mehrfach das gleiche `split()`\en könnte man sich sparen.

Du hast es ja selbst schon als Warnung im Docstring geschrieben: Das berechnen von Mittelpunkten sollte nicht auch gleichzeitig Dateien löschen. Das ist unerwartet. Und eine Shell mit einem externen Programm zum Löschen von Dateien!? Dafür gibt es `os.remove()`. Das Problem könnte entstehen, wenn Du ein `glob()` ausführst und das einen Dateinamen erwischt, für den schon eine asynchrone "Lösch-Shell" läuft. Übrigens weiss ich jetzt gerade nicht, ob `glob()` eine Sortierung garantiert!?

Der Name `filelist` ist problematisch weil es erstens keine Liste von Dateien, sondern Datei*namen* ist, und zweitens sollte man vermeiden Typen in die Namen zu schreiben. Ein besserer Name wäre `filenames`. Das gleiche gilt für Funktionsnamen. `calculateCenters()` sagt genau so viel aus, man kann die Methode aber zu einer Generator-Funktion umbauen, ohne dass der Name falsch wird.

Verfasst: Sonntag 25. Oktober 2009, 22:33
von Nko
BlackJack hat geschrieben:@Nko: Als erstes fällt natürlich auf, dass Namensgebung und Leerzeichensetzung nicht PEP8-Konform ist.
Danke, räusper, das mit dem Pep8 hab ich noch nicht so verinnerlicht. Da ist viel C-style dabei gewesen. Hab's also mal überarbeitet und auch einiges geändert. Jetzt läufts auch recht rund und ich hoffe es ist auch schöner zu lesen.

Jetzt hab ich noch was neues eingebaut: Bildschirm löschen ('n') und Quit ('q'). Ausserdem muss "motion" jetzt nicht mehr extern gestartet werden. Aber "killall" zum beenden ist nicht elegant. Ich check das nicht, wie ich den Prozess beenden soll. Einen Hinweis vielleicht?

Code: Alles auswählen

import pygame
import os
from glob import glob

picturePath = "/dev/shm/pics/motionCap*.jpg"
motionConfigFilename = "/.../motion.conf"

def drawMyCircle(window, center):
    """draws a white circle on 'window' at position 'center' with size 3""" 
    pygame.draw.circle(window, (255, 255, 255), center, 3)
    pygame.display.flip()
    
def parsePositionsFromFilenames(filenames):
    """receives positions of movement by parsing all filenames in 'fileNames'
    returns a list of positions"""    
    centers = []
    for name in filenames:
        splitName = name.split("_")
        newx = int(splitName[3])
        newy = int(splitName[4].split("m")[0])
        centers.append((newx, newy))
    return centers

def deleteFiles(filenames):
    """deletes all files in 'fileNames' from disc
    use with care!!!"""
    for filename in filenames:
        os.remove(filename)
    return

def main(window,path):
    """reads constantly all files from wildcarded string 'path'
    draws a circle on 'window' and reads some events"""
    quit = False
    while not quit: 
        filenames = glob(path)
        centres = parsePositionsFromFilenames(filenames)
        for coordinates in centres:
            drawMyCircle(window, coordinates)
        deleteFiles(filenames)
        for event in pygame.event.get():
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_n:
                    window.fill((0, 0, 0))
                    pygame.display.flip()
                if event.key == pygame.K_q:
                    quit = True

if __name__ == "__main__":
    pygame.init() 
    window = pygame.display.set_mode((640, 480)) 
    pid = os.fork()
    if not pid:
        os.execvp("/usr/bin/motion",["motion", "-c", motionConfigFilename])
    main(window,picturePath)
    os.execvp("killall", ["killall", "motion"])

Verfasst: Sonntag 25. Oktober 2009, 23:11
von BlackJack
`os.kill()` hast Du jetzt nicht gefunden? Und selbst wenn Du ein externes Programm verwendest, warum dann nicht ``kill`` mit der PID füttern, die Du ja hast!?

Programme starten sollte man mit dem `subprocess`-Modul und nicht mit den diversen "low level"-Möglichkeiten in `os`.

Verfasst: Donnerstag 26. November 2009, 18:20
von iceman21
HALLO
Ich würde dein Programm rein aus interesse gerne mal testen. Hast du vielleicht ein kleines Beispiel video das du mal hochladen kannst? ich habe keine kamera hier. dann kann ich das mal laufen lassen :)
Danke