Mikroskop mit Tkinter-GUi

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo,
habe bei einem großen Auktionshaus ein USB-Mikroskop gekauft und es war nur eine Software für Windows dabei. Da sie aber so schön günstig war (20 €), wurde sie erworben und ich habe gehofft etwas mit OpenCV basteln zu können. Ist soweit recht brauchbar geworden und für meine Bedürfnisse ausreichend. Was muß so ein Ding noch können - Filter oder so ?

CAM STOP/STOP --> "||"
ZOOM + --> "+"
ZOOM - --> "-"
ZOOM NORMAL --> "+/-"
AUFNAHME FOTO --> "#"

Gruß Frank

[codebox=python file=Unbenannt.txt]#! /usr/bin/env python
# -*- coding: utf-8
import Tkinter as tk
import cv2
from time import gmtime, strftime
from PIL import Image, ImageTk
from functools import partial

WIDTH = 640
HEIGHT = 480
PATH = "microscop/"
FORMAT = "tiff"
ZOOM_STEP = 10

class Microscope(object):

PROPID_WIDTH = 3
PROPID_HEIGHT = 4

def __init__(self, cam_id):
self.cam = cv2.VideoCapture(cam_id)
if self.cam.isOpened():
self.width = self.cam.get(self.PROPID_WIDTH)
self.height = self.cam.get(self.PROPID_HEIGHT)
self.aspect_ratio = self.width / self.height

def get_image(self):
if self.cam.isOpened():
return Image.frombytes("RGB", (int(self.cam.get(
self.PROPID_WIDTH)), int(self.cam.get(self.PROPID_HEIGHT))),
self.cam.read()[1],"raw", "BGR").resize((self.width,
self.height))
else:
return None

def take_picture(self, path):
image = self.get_image().resize((self.width, self.height))
image.save(path)

def zoom_image(self, step):
width = int(self.width + step * self.aspect_ratio)
height = int(self.height + step)

if width > self.cam.get(self.PROPID_WIDTH)\
and height > self.cam.get(self.PROPID_HEIGHT)\
or height > self.cam.get(self.PROPID_HEIGHT) \
and width > self.cam.get(self.PROPID_WIDTH) :
self.width = width
self.height = height

def reset_zoom(self):
self.width = self.cam.get(self.PROPID_WIDTH)
self.height = self.cam.get(self.PROPID_HEIGHT)

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

def release(self):
self.cam.release()


class MicroscopeUI(tk.Frame):

UPDATE_INTERVAL = 10

def __init__(self, parent, width, height, path, format, zoom_step):
tk.Frame.__init__(self, parent)
self.parent = parent
self.width = width
self.height = height
self.path = path
self.format = format
self.tk_image = None
self.microscope = Microscope(-1)
self.canvas = tk.Canvas(self, width=width, height=height)
self.canvas.grid(column=0, row=0)
vscrollbar = tk.Scrollbar(self)
vscrollbar.grid(column=1, row=0, sticky=tk.N+tk.S)
self.canvas.config(yscrollcommand=vscrollbar.set)
vscrollbar.config(command=self.canvas.yview)
hscrollbar = tk.Scrollbar(self, orient=tk.HORIZONTAL)
hscrollbar.grid(column=0, row=1, columnspan=5, sticky=tk.E+tk.W)
self.canvas.config(xscrollcommand=hscrollbar.set)
hscrollbar.config(command=self.canvas.xview)
self.buttons = list()
button_frame = tk.Frame(self)
button_frame.grid(column=0, row=3, columnspan=2)
for column, (text, command, var) in enumerate(
(("||", self.start_stop, None),
("+", self.microscope.zoom_image, zoom_step),
("-", self.microscope.zoom_image, -zoom_step),
("+/-", self.microscope.reset_zoom, None),
("#", self.take_picture, None))):
button = tk.Button(button_frame, text=text, width=4,
relief="raised", font="Arial 10 bold")
button.grid(column=column, row=0)
self.buttons.append(button)
if var:
button.config(command=partial(command, var))
else:
button.config(command=command)

def start_stop(self):
if self.after_id is None:
self.buttons[0].config(text = "||")
self.run()
else:
self.buttons[0].config(text = ">")
self.after_cancel(self.after_id)
self.after_id = None

def run(self):
self.tk_image = self.microscope.get_image()
if self.tk_image:
self.canvas.delete("img")
self.tk_image = ImageTk.PhotoImage(self.tk_image)
self.canvas.create_image((0,0), anchor=tk.NW, image=self.tk_image,
tag="img")
width, height = self.microscope.get_size()
self.canvas.config(scrollregion = (0, 0, width, height))
self.after_id = self.after(self.UPDATE_INTERVAL, self.run)
else:
self.canvas.create_text((self.width / 2, self.height / 2),
text='NO CAM', font='Arial 40')
for button in self.buttons:
button.config(state="disabled")

def take_picture(self, event = None):
self.microscope.take_picture("{0}{1}.{2}".format(self.path, strftime(
"%d%b%Y_%H_%M_%S", gmtime()), self.format))

def release(self):
self.microscope.release()
self.parent.destroy()

def main():
root = tk.Tk()
root.title('MICROSCOPE')
microscope_ui = MicroscopeUI(root, WIDTH, HEIGHT, PATH, FORMAT, ZOOM_STEP)
microscope_ui.pack(expand=tk.YES)
microscope_ui.run()
root.protocol("WM_DELETE_WINDOW", microscope_ui.release)
root.mainloop()
if __name__ == '__main__':
main()[/code]

Gruß Frank
BlackJack

@kaytec: `Microscope` ist nicht robust. Wenn die Kamera nicht ”offen” ist, dann werden nicht alle Attribute definiert und damit gibt es in einigen Methoden `AttributeError`\s. Die `__init__()` sollte ein benutzbares Objekt hinterlassen. Also am besten eine Ausnahme auslösen wenn die Kamera nicht offen ist, denn dann ist das Objekt sowieso unbrauchbar.

Wenn es eine externe Ressource gibt, die man explizit wieder freigeben muss, dann bietet es sich an aus dem Objekt das diese Ressource kapselt einen „context manager“ zu machen, damit man ``with`` verwenden kann um sicherzustellen, dass die Ressource auch tatsächlich wieder freigegeben wird.

Ich gehe mal davon aus das sich die Höhe und Breite vom Kamerabild nicht ändert. Trotzdem wird das sehr oft im Code abgefragt. Sollte man es doch immer live abfragen müssen, würde ich da Properties draus machen, damit das nicht immer wieder im Quelltext stehen muss.

Beim `Image.frombytes()` hilft die Formatierung nicht unbedingt dabei zu sehen wo die Argumente anfangen und aufhören. Die Methode prüft nicht ob tatsächlich ein Bild gelesen wurde.

Die ``if``-Bedingung in der Zoom-Methode ist komisch. Sehe ich das falsch oder fragst Du da zweimal das gleiche ab, nur in umgekehrter Reihenfolge.

Ungetestet:

Code: Alles auswählen

class Microscope(object):
   
    PROPID_WIDTH = 3
    PROPID_HEIGHT = 4
   
    def __init__(self, cam_id):
        self.cam = cv2.VideoCapture(cam_id)
        if not self.cam.isOpened():
            raise RuntimeError('can not open camera {0!r}'.format(cam_id))
        self.cam_width = int(self.cam.get(self.PROPID_WIDTH))
        self.cam_height = int(self.cam.get(self.PROPID_HEIGHT))
        self.width = self.height = None
        self.reset_zoom()
        self.aspect_ratio = self.width / self.height
    
    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.release()

    @property
    def size(self):
        return (self.width, self.height)

    def get_image(self):
        is_ok, image_data = self.cam.read()
        if is_ok:
            return Image.frombytes(
                'RGB',
                (self.cam_width, self.cam_height),
                image_data,
                'raw',
                'BGR'
            ).resize(self.size)
        else:
            return None
       
    def take_picture(self, path):
        self.get_image().resize(self.size).save(path)
           
    def zoom_image(self, step):
        width = int(self.width + step * self.aspect_ratio)
        height = int(self.height + step)
        if width > self.cam_width and height > self.cam_height:
            self.width = width
            self.height = height
               
    def reset_zoom(self):
        self.width = self.cam_width
        self.height = self.cam_height
               
    def release(self):
        self.cam.release()
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo BlackJack,

Bei mir hat es nie einen `AttributeError` geworfen - kann es auch gerade nicht mit Kamera testen, denn in meinem gebrauchten Leasingrückläufer haben schlaue Leute Kamera/Mikro/Bluetooth nicht eingebaut. Das mit dem benutzbaren Objekt ist jetzt für mich ziemlich egal aber bei gezeigtem Code sollte es klargestellt werden.

Den zweiten Absatz verstehe ich mal gar nicht.

Es gibt ja immer drei Bildgrössen (tatsächliches Kamerabild / das veränderte (Zoom) / Die Bildgrösse in der GUI --> wird dir klar sein) und daher frage ich sie bestimmt immer wieder ab - das Script ist schon älter und es hat einer die Doku vergessen. Das mit "@property" sieht echt immer gut aus, doch habe ich die Verwendung nie so verstanden. Die Methoden mit dem Unterstrich sind auch irgendwie schön, doch auch hier .... - was macht es ?

Ob ein Bild wirklich gelesen wird gibt die Abfrage nicht her --> war hier auch schon das Problem viewtopic.php?f=6&t=37926&p=291671#p291671

Die Abfrage in der "zoom_image" Methode beruhte aus dem Problem, dass bei dem Unterschreiten der tatsächlichen Bildgrösse immer ein step zu viel war und daher habe ich es so hingebastelt. Es wird bestimmt nach deiner Veränderung auch gehen.

Gruß und Dank Frank
BlackJack

@kaytec: Wenn die Kamera nicht geöffnet ist, dann werden die Attribute `witdh`, `height`, und `aspect_ratio` nicht erstellt. `take_picture()`, `zoom_image()` und `get_size()` greifen darauf aber zu, das heisst die werden dann einen `AttributeError` auslösen.

Die Kamera ist eine externe Ressource die man wieder freigeben muss wenn man damit fertig ist. Python weiss nicht das man `realease()` aufrufen muss wenn das `Microscope`-Objekt nicht mehr benötigt wird. Um eine saubere Verwendung von solchen externen Ressourcen sicherzustellen hat Python die ``with``-Anweisung und das „context manager“-Protokoll das Datentypen verwenden um mit ``with`` zu interagieren. Das sind die `__enter__()`- und `__exit__()`-Methoden die beim betreten und verlassen eines ``with``-Blocks aufgerufen werden.

Code: Alles auswählen

      # ...
      with Microscope(-1) as microscope:
            microscope_ui = MicroscopeUI(
                  root, microscope, WIDTH, HEIGHT, PATH, FORMAT, ZOOM_STEP
            )
            # ...
            root.mainloop()
Solange sich das tatsächliche Kamerabild in der grösse nicht ändert, braucht man es nicht jedes mal abfragen. Und falls das doch der Fall sein sollte, dann würde man den Code dafür nicht in jede Methode schreiben, sondern Getter, beziehungsweise in Python dann eher Properties.

Wenn das Flag sich nicht an die Dokumentation hält sollte man es trotzdem nicht ignorieren. Es kann ja trotzdem noch Fälle geben wo tatsächlich `False` zurückgegeben wird. Und vielleicht fixt ja mal irgendwann jemand den Fehler in OpenCV bzw. der Python-Anbindung.

Bei der Zoom-Bedingung habe ich ja nichts an der Bedingung verändert, also ich habe nicht eine vorhandene Bedingung irgendwie anders formuliert, sondern nur den sehr offensichtlich redundanten Teil weg gelassen. Und dann jetzt aber nachgefragt ob das denn überhaupt so sein sollte, oder ob Du da nicht vielleicht einen Fehler gemacht hast. Also das irgendwo width vielleicht height heissen sollte, eben so das da nicht zweimal das gleiche geprüft wird.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo BlackJack,
danke für deine ausführlichen Erklärungen und falls ich de Zeit finde, werde ich versuchen es umzuschreiben.

Gruß Frank und Dank
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo,

nach langer Zeit mal wieder gebastelt und versucht die Verbesserungen einzubauen. Das Aufnehmen von Videos ist neu, doch wenn es in in Echtzeit sein soll, dann müsste die zeitliche Steuerung mit einem Nebenthread (sagt man glaub ich so ?) gelöst werden. Ich mache mit meine Tochter Aufnahmen von Uhrwerke, kleinen Lebewesen etc. Für solche Spielereien langt es und macht recht viel Spaß. Für die Steuerung der Aufnahme verwende ich den Farbstatus des Buttons und ist bestimmt eine richtige Bastelei !?
[codebox=python file=Unbenannt.txt]
#! /usr/bin/env python
# -*- coding: utf-8

try:
import tkinter as tk
except ImportError:
import Tkinter as tk

import time
import datetime
import cv2
from PIL import Image, ImageTk
from functools import partial
WIDTH = 640
HEIGHT = 480


class Microscope(object):

PROPID_WIDTH = 3
PROPID_HEIGHT = 4

def __init__(self, cam_id = -1):
self.cam = cv2.VideoCapture(cam_id)
self.recording = False
if not self.cam.isOpened():
raise RuntimeError('can not open camera {0!r}'.format(
cam_id))
self.cam_width = self.width = self.cam.get(self.PROPID_WIDTH)
self.cam_height = self.height = self.cam.get(self.PROPID_HEIGHT)
self.aspect_ratio = self.width / self.height

def __enter__(self):
return self

def __exit__(self, *args):
self.release()

@property
def size(self):
return (self.width, self.height)

def get_image(self):
state, frame = self.cam.read()
if state:
image = Image.frombytes("RGB", (int(self.cam_width),
int(self.cam_height)) ,frame, "raw", "BGR"
).resize(self.size)
if self.recording:
self.video_writer.write(frame)
return image
else:
return None

def recording_start_stop(self, state, path = ""):
self.recording = state
if self.recording:
self.video_writer = cv2.VideoWriter(path, cv2.cv.CV_FOURCC(
*"XVID"), 24, (int(self.cam_width),
int(self.cam_height)))

def take_picture(self, path):
image = self.get_image().resize((self.width, self.height))
image.save(path)

def zoom_image(self, step):
width = int(self.width + step * self.aspect_ratio)
height = int(self.height + step)
if width > 0 and height > 0:
self.width = width
self.height = height

def reset_zoom(self):
self.width, self.height = self.cam_width, self.cam_height

def release(self):
self.cam.release()


class MicroscopeUI(tk.Frame):

UPDATE_INTERVAL = 10

def __init__(self, parent, microscope, width, height,
zoom_step = 10, picture_path = "", video_path = ""):
tk.Frame.__init__(self, parent)
self.parent = parent
self.width = width
self.height = height
self.picture_path = picture_path
self.video_path = video_path
self.tk_image = None
self.buttons = list()
self.microscope = microscope
self.canvas = tk.Canvas(self, width=width, height=height)
self.canvas.grid(column=0, row=0)
vscrollbar = tk.Scrollbar(self)
vscrollbar.grid(column=1, row=0, sticky=tk.N+tk.S)
self.canvas.config(yscrollcommand=vscrollbar.set)
vscrollbar.config(command=self.canvas.yview)
hscrollbar = tk.Scrollbar(self, orient=tk.HORIZONTAL)
hscrollbar.grid(column=0, row=1, columnspan=5, sticky=tk.E+tk.W)
self.canvas.config(xscrollcommand=hscrollbar.set)
hscrollbar.config(command=self.canvas.xview)
button_frame = tk.Frame(self)
button_frame.grid(column=0, row=3, columnspan=2)
for column, (text, command, var)in enumerate(
(("||", self.start_stop, None),
("+", self.microscope.zoom_image, zoom_step),
("-", self.microscope.zoom_image, -zoom_step),
("+/-", self.reset_zoom, None),
("[ ]", self.take_picture, None),
("REC", self.recording_film, None))):
button = tk.Button(button_frame, text=text, width=4,
relief="raised", font="Arial 10 bold")
button.grid(column=column, row=0)
self.buttons.append(button)
if var:
button.config(command=partial(command, var))
else:
button.config(command=command)
self.buttons[-1].config(bg = "lightgreen")

def start_stop(self):
if self.after_id is None:
self.buttons[0].config(text = "||")
self.run()
else:
self.buttons[0].config(text = ">")
self.after_cancel(self.after_id)
self.after_id = None

def run(self):
self.tk_image = self.microscope.get_image()
if self.tk_image:
self.canvas.delete("img")
self.tk_image = ImageTk.PhotoImage(self.tk_image)
self.canvas.create_image((0,0), anchor=tk.NW,
image=self.tk_image, tag="img")
width, height = self.microscope.size
self.canvas.config(scrollregion = (0, 0, width, height))
self.after_id = self.after(self.UPDATE_INTERVAL, self.run)
else:
self.canvas.create_text((self.width / 2, self.height / 2),
text='NO CAM', font='Arial 40')
for button in self.buttons:
button.config(state="disabled")

def reset_zoom(self):
self.microscope.reset_zoom()

def recording_film(self):
if self.buttons[-1].config("bg")[-1] == "lightgreen":
self.buttons[-1].config(bg = "red")
self.microscope.recording_start_stop(True,
"{0}{1:%d%b%Y_%H_%M_%S.%f}.avi".format(self.video_path,
datetime.datetime.utcnow()))
else:
self.buttons[-1].config(bg = "lightgreen")
self.microscope.recording_start_stop(False)

def take_picture(self):
self.microscope.take_picture("{0}{1:%d%b%Y_%H_%M_%S.%f}.tiff"
.format(self.picture_path, datetime.datetime.utcnow()))

def release(self):
self.parent.destroy()

def main():
root = tk.Tk()
root.title('MICROSCOPE')
with Microscope() as microscope:
microscope_ui = MicroscopeUI(
root, microscope, WIDTH, HEIGHT)
microscope_ui.pack(expand=tk.YES)
microscope_ui.run()
root.protocol("WM_DELETE_WINDOW", microscope_ui.release)
root.mainloop()
if __name__ == '__main__':
main()
[/code]

Gruß Frank
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo,

wollte die Ausnahme in der Haupt-Gui verarbeiten, doch nehme ich den "mainloop" aus der "with" Anweisung, dann löst er auch bei einer Nichtauslösung des "except" Blockes, die "__exit__" Funktion aus und somit brauche ich einen zweiten "mainloop". Werden ja nur nach Bedarf gestartet, doch sollte es keine zwei geben !?

Gruß Frank

Code: Alles auswählen

def main():
    root = tk.Tk()
    root.title('MICROSCOPE')
    try:
        with Microscope(DEFAULT_CAM_ID) as microscope:
            microscope_ui = MicroscopeUI(
                root, microscope, WIDTH, HEIGHT)
            microscope_ui.pack(expand=tk.YES)
            microscope_ui.run()
            root.protocol("WM_DELETE_WINDOW", microscope_ui.release)
            root.mainloop()

    except RuntimeError:
        tk.Label(root, text = 'can not open camera {0!r}'.format(
                DEFAULT_CAM_ID), font = "Arial 20", height = 10).pack()
        root.mainloop()
    

if __name__ == '__main__':
    main()
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Schön ist es nicht, eine wirklich bessere Lösung habe ich aber auch nicht. Aber das verbot der zwei Mainloops bezieht sich nicht auf die Anzahl der Aufrufe, sondern den Zeitpunkt. Insofern schon ok.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo,

das Zoom hat nicht funktioniert und ich habe es umgebastelt und die Größe des Zooms auch zum "property" gemacht.

Code: Alles auswählen

#! /usr/bin/env python
# -*- coding: utf-8

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

import time
import datetime
import cv2
from PIL import Image, ImageTk
from functools import partial
WIDTH = 640
HEIGHT = 480
VIDEO_CODEC = "XVID"
DEFAULT_CAM_ID = 1

class Microscope(object):

    PROPID_WIDTH = 3
    PROPID_HEIGHT = 4

    def __init__(self, cam_id = id):
        self.cam = cv2.VideoCapture(cam_id)
        self.recording = False
        if not self.cam.isOpened():
            raise RuntimeError("can not open camera {0!r}".format(
                cam_id))
        self.cam_width = self.zoom_width = self.cam.get(self.PROPID_WIDTH)
        self.cam_height = self.zoom_height = self.cam.get(self.PROPID_HEIGHT)
        self.aspect_ratio = self.zoom_width / self.zoom_height
        

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.release()

    @property
    def size(self):
        return (int(self.cam_width), int(self.cam_height))
        
    @property
    def zoom_size(self):
        return (int(self.zoom_width), int(self.zoom_height))

    def get_image(self):
        state, frame = self.cam.read()
        if state:
            image = Image.frombytes("RGB", self.size ,frame,
                "raw", "BGR").resize(self.zoom_size)
            if self.recording:
                self.video_writer.write(frame)
            return image
        else:
            return state

    def recording_start_stop(self, state, path = ""):
        self.recording = state
        if self.recording:
            self.video_writer = cv2.VideoWriter(path, cv2.cv.CV_FOURCC(
                * VIDEO_CODEC), 24, (self.size))

    def take_picture(self, path):
        self.get_image().save(path)

    def zoom_image(self, step):
        width = int(self.zoom_width + step * self.aspect_ratio)
        height = int(self.zoom_height + step)
        if width > 0 and height > 0:
            self.zoom_width = width
            self.zoom_height = height

    def reset_zoom(self):
        self.zoom_width, self.zoom_height = self.size

    def release(self):
        self.cam.release()

class MicroscopeUI(tk.Frame):

    UPDATE_INTERVAL = 100

    def __init__(self, parent, microscope, width, height, 
        zoom_step = 10, picture_path = "", video_path = ""):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.width = width
        self.height = height
        self.picture_path = picture_path
        self.video_path = video_path
        self.tk_image = None
        self.buttons = list()
        self.microscope = microscope
        self.canvas = tk.Canvas(self, width=width, height=height)
        self.canvas.grid(column=0, row=0)
        vscrollbar = tk.Scrollbar(self)
        vscrollbar.grid(column=1, row=0, sticky=tk.N+tk.S)
        self.canvas.config(yscrollcommand=vscrollbar.set)
        vscrollbar.config(command=self.canvas.yview)
        hscrollbar = tk.Scrollbar(self, orient=tk.HORIZONTAL)
        hscrollbar.grid(column=0, row=1, columnspan=5, sticky=tk.E+tk.W)
        self.canvas.config(xscrollcommand=hscrollbar.set)
        hscrollbar.config(command=self.canvas.xview)
        button_frame = tk.Frame(self)
        button_frame.grid(column=0, row=3, columnspan=2)
        for column, (text, command, var)in enumerate(
            (("||", self.start_stop, None),
             ("+", self.microscope.zoom_image, zoom_step),
             ("-", self.microscope.zoom_image, -zoom_step),
             ("+/-", self.reset_zoom, None),
             ("[ ]", self.take_picture, None),
             ("REC", self.recording_film, None))):
            button = tk.Button(button_frame, text=text, width=4,
                relief="raised", font="Arial 10 bold")
            button.grid(column=column, row=0)
            self.buttons.append(button)
            if var:
                 button.config(command=partial(command, var))
            else:
                 button.config(command=command)
        self.buttons[-1].config(bg = "lightgreen")

    def start_stop(self):
        if self.after_id is None:
            self.buttons[0].config(text = "||")
            self.run()
        else:
            self.buttons[0].config(text = ">")
            self.after_cancel(self.after_id)
            self.after_id = None

    def run(self):
        self.tk_image = self.microscope.get_image()
        if self.tk_image:
            self.canvas.delete("img")
            self.tk_image = ImageTk.PhotoImage(self.tk_image)
            self.canvas.create_image((0,0), anchor=tk.NW,
                image=self.tk_image, tag="img")
            width, height = self.microscope.zoom_size
            self.canvas.config(scrollregion = (0, 0, width, height))
            self.after_id = self.after(self.UPDATE_INTERVAL, self.run)
        else:
            self.raise_cam_id_error()
                
    def raise_cam_id_error(self):
        self.canvas.create_text((self.width / 2, self.height / 2),
            text='NO CAM', font='Arial 40')
        for button in self.buttons:
            button.config(state="disabled")

    def reset_zoom(self):
        self.microscope.reset_zoom()

    def recording_film(self):
        if self.buttons[-1].config("bg")[-1] == "lightgreen":
            self.buttons[-1].config(bg = "red")
            self.microscope.recording_start_stop(True, 
            "{0}{1:%d%b%Y_%H_%M_%S.%f}.avi".format(self.video_path, 
            datetime.datetime.utcnow()))
        else:
            self.buttons[-1].config(bg = "lightgreen")
            self.microscope.recording_start_stop(False)

    def take_picture(self):
        self.microscope.take_picture("{0}{1:%d%b%Y_%H_%M_%S.%f}.tiff"
            .format(self.picture_path, datetime.datetime.utcnow()))

    def release(self):
        self.parent.destroy()

def main():
    root = tk.Tk()
    root.title('MICROSCOPE')
    try:
        with Microscope(DEFAULT_CAM_ID) as microscope:
            microscope_ui = MicroscopeUI(
                root, microscope, WIDTH, HEIGHT)
            microscope_ui.pack(expand=tk.YES)
            microscope_ui.run()
            root.protocol("WM_DELETE_WINDOW", microscope_ui.release)
            root.mainloop()

    except RuntimeError:
        tk.Label(root, text = 'can not open camera {0!r}'.format(
                DEFAULT_CAM_ID), font = "Arial 20", height = 10).pack()
        root.mainloop()
    
if __name__ == '__main__':
    main()
Gruß Frank
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo,

Die Interpolationsmethode kann jetzt gewählt werden und die Bilder/Filme werden gezoomt gespeichert.

[codebox=python file=Unbenannt.txt]
#!/usr/bin/env python
# -*- coding: utf-8
from __future__ import division

try:
import tkinter as tk
except ImportError:
import Tkinter as tk

import time
import datetime
import cv2
from PIL import Image, ImageTk
from functools import partial
from itertools import cycle

WIDTH = 640
HEIGHT = 480
VIDEO_CODEC = "XVID"
DEFAULT_CAM_ID = 1

class Microscope(object):

PROPID_WIDTH = 3
PROPID_HEIGHT = 4

def __init__(self, cam_id = id):
self.cam = cv2.VideoCapture(cam_id)
self.recording = False
if not self.cam.isOpened():
raise RuntimeError("can not open camera {0!r}".format(
cam_id))
self.interpolation_methods = {"NEAREST" : cv2.INTER_NEAREST,
"LINEAR" : cv2.INTER_LINEAR,
"AREA" : cv2.INTER_AREA,
"CUBIC" : cv2.INTER_CUBIC,
"LANCZOS4" : cv2.INTER_LANCZOS4}
self.interpolation_methods_keys = cycle(self.interpolation_methods.keys())
self.interpolation_method = self.interpolation_methods_keys.next()
self.cam_width = self.zoom_width = self.cam.get(self.PROPID_WIDTH)
self.cam_height = self.zoom_height = self.cam.get(self.PROPID_HEIGHT)
self.aspect_ratio = self.zoom_width / self.zoom_height
self.aspect_ratio = self.zoom_width / self.zoom_height


def __enter__(self):
return self

def __exit__(self, *args):
self.release()

@property
def size(self):
return (int(self.cam_width), int(self.cam_height))

@property
def zoom_size(self):
return (int(self.zoom_width), int(self.zoom_height))

@property
def interpolation(self):
return self.interpolation_method

def get_image(self):
state, frame = self.cam.read()

inter_frame = cv2.resize(frame, self.zoom_size,
interpolation = self.interpolation_methods[
self.interpolation_method])

if state:
image = Image.frombytes("RGB", self.zoom_size, inter_frame,
"raw", "BGR")
if self.recording:
self.video_writer.write(inter_frame)
return image
else:
return state

def recording_start_stop(self, state, path = ""):
self.recording = state
if self.recording:
self.video_writer = cv2.VideoWriter(path, cv2.cv.CV_FOURCC(
* VIDEO_CODEC), 24, self.zoom_size)

def take_picture(self, path):
self.get_image().save(path)

def zoom_image(self, step):
width = int(self.zoom_width + step * self.aspect_ratio)
height = int(self.zoom_height + step)
if width > 0 and height > 0:
self.zoom_width = width
self.zoom_height = height

def reset_zoom(self):
self.zoom_width, self.zoom_height = self.size

def set_interpolation_method(self):
self.interpolation_method = self.interpolation_methods_keys.next()

def release(self):
self.cam.release()

class MicroscopeUI(tk.Frame):

UPDATE_INTERVAL = 50

def __init__(self, parent, microscope, width, height,
zoom_step = 10, picture_path = "", video_path = ""):
tk.Frame.__init__(self, parent)
self.parent = parent
self.width = width
self.height = height
self.picture_path = picture_path
self.video_path = video_path
self.tk_image = None
self.buttons = list()
self.microscope = microscope
self.canvas = tk.Canvas(self, width=width, height=height)
self.canvas.grid(column=0, row=0)
vscrollbar = tk.Scrollbar(self)
vscrollbar.grid(column=1, row=0, sticky=tk.N+tk.S)
self.canvas.config(yscrollcommand=vscrollbar.set)
vscrollbar.config(command=self.canvas.yview)
hscrollbar = tk.Scrollbar(self, orient=tk.HORIZONTAL)
hscrollbar.grid(column=0, row=1, columnspan=5, sticky=tk.E+tk.W)
self.canvas.config(xscrollcommand=hscrollbar.set)
hscrollbar.config(command=self.canvas.xview)
button_frame = tk.Frame(self)
button_frame.grid(column=0, row=3, columnspan=2)
for column, (text, command, var)in enumerate(
(("||", self.start_stop, None),
("+", self.microscope.zoom_image, zoom_step),
("-", self.microscope.zoom_image, -zoom_step),
("+/-", self.reset_zoom, None),
("[ ]", self.take_picture, None),
("REC", self.recording_film, None),
(self.microscope.interpolation, self.set_interpolation_method
, None))):
button = tk.Button(button_frame, text=text, width=7,
relief="raised", font="Arial 10 bold")
button.grid(column=column, row=0)
self.buttons.append(button)
if var:
button.config(command=partial(command, var))
else:
button.config(command=command)
self.buttons[5].config(bg = "lightgreen")

def start_stop(self):
if self.after_id is None:
self.buttons[0].config(text = "||")
self.run()
else:
self.buttons[0].config(text = ">")
self.after_cancel(self.after_id)
self.after_id = None

def run(self):
self.tk_image = self.microscope.get_image()
if self.tk_image:
self.canvas.delete("img")
self.tk_image = ImageTk.PhotoImage(self.tk_image)
self.canvas.create_image((0,0), anchor=tk.NW,
image=self.tk_image, tag="img")
width, height = self.microscope.zoom_size
self.canvas.config(scrollregion = (0, 0, width, height))
self.after_id = self.after(self.UPDATE_INTERVAL, self.run)
else:
self.raise_cam_id_error()

def raise_cam_id_error(self):
self.canvas.create_text((self.width / 2, self.height / 2),
text='NO CAM', font='Arial 40')
for button in self.buttons:
button.config(state="disabled")

def reset_zoom(self):
self.microscope.reset_zoom()

def set_interpolation_method(self):
self.microscope.set_interpolation_method()
self.buttons[-1].config(text = self.microscope.interpolation)



def recording_film(self):
if self.buttons[5].config("bg")[-1] == "lightgreen":
self.buttons[5].config(bg = "red")
self.microscope.recording_start_stop(True,
"{0}{1:%d%b%Y_%H_%M_%S.%f}.avi".format(self.video_path,
datetime.datetime.utcnow()))
else:
self.buttons[5].config(bg = "lightgreen")
self.microscope.recording_start_stop(False)

def take_picture(self):
self.microscope.take_picture("{0}{1:%d%b%Y_%H_%M_%S.%f}.tiff"
.format(self.picture_path, datetime.datetime.utcnow()))

def release(self):
self.parent.destroy()

def main():
root = tk.Tk()
root.title('MICROSCOPE')
try:
with Microscope(DEFAULT_CAM_ID) as microscope:
microscope_ui = MicroscopeUI(
root, microscope, WIDTH, HEIGHT)
microscope_ui.import time
import datetime
import cv2
from PIL import Image, ImageTk
from functools import partial
from itertools import cycle

WIDTH = 640
HEIGHT = 480
VIDEO_CODEC = "XVID"
DEFAULT_CAM_ID = 1

class Microscope(object):

PROPID_WIDTH = 3
PROPID_HEIGHT = 4

def __init__(self, cam_id = id):
self.cam = cv2.VideoCapture(cam_id)
self.recording = False
if not self.cam.isOpened():
raise RuntimeError("can not open camera {0!r}".format(
cam_id))
self.interpolation_methods = {"NEAREST" : cv2.INTER_NEAREST,
"LINEAR" : cv2.INTER_LINEAR,
"AREA" : cv2.INTER_AREA,
"CUBIC" : cv2.INTER_CUBIC,
"LANCZOS4" : cv2.INTER_LANCZOS4}
self.interpolation_methods_keys = cycle(self.interpolation_methods.keys())
self.interpolation_method = self.interpolation_methods_keys.next()
self.cam_width = self.zoom_width = self.cam.get(self.PROPID_WIDTH)
self.cam_height = self.zoom_height = self.cam.get(self.PROPID_HEIGHT)
self.aspect_ratio = self.zoom_width / self.zoom_height
self.aspect_ratio = self.zoom_width / self.zoom_height


def __enter__(self):
return self

def __exit__(self, *args):
self.release()

@property
def size(self):
return (int(self.cam_width), int(self.cam_height))

@property
def zoom_size(self):
return (int(self.zoom_width), int(self.zoom_height))

@property
def interpolation(self):
return self.interpolation_method

def get_image(self):
state, frame = self.cam.read()

inter_frame = cv2.resize(frame, self.zoom_size,
interpolation = self.interpolation_methods[
self.interpolation_method])

if state:
image = Image.frombytes("RGB", self.zoom_size, inter_frame,
"raw", "BGR")
if self.recording:
self.video_writer.write(inter_frame)
return image
else:
return state

def recording_start_stop(self, state, path = ""):
self.recording = state
if self.recording:
self.video_writer = cv2.VideoWriter(path, cv2.cv.CV_FOURCC(
* VIDEO_CODEC), 24, self.zoom_size)

def take_picture(self, path):
self.get_image().save(path)

def zoom_image(self, step):
width = int(self.zoom_width + step * self.aspect_ratio)
height = int(self.zoom_height + step)
if width > 0 and height > 0:
self.zoom_width = width
self.zoom_height = height

def reset_zoom(self):
self.zoom_width, self.zoom_height = self.size

def set_interpolation_method(self):
self.interpolation_method = self.interpolation_methods_keys.next()

def release(self):
self.cam.release()

class MicroscopeUI(tk.Frame):

UPDATE_INTERVAL = 50

def __init__(self, parent, microscope, width, height,
zoom_step = 10, picture_path = "", video_path = ""):
tk.Frame.__init__(self, parent)
self.parent = parent
self.width = width
self.height = height
self.picture_path = picture_path
self.video_path = video_path
self.tk_image = None
self.buttons = list()
self.microscope = microscope
self.canvas = tk.Canvas(self, width=width, height=height)
self.canvas.grid(column=0, row=0)
vscrollbar = tk.Scrollbar(self)
vscrollbar.grid(column=1, row=0, sticky=tk.N+tk.S)
self.canvas.config(yscrollcommand=vscrollbar.set)
vscrollbar.config(command=self.canvas.yview)
hscrollbar = tk.Scrollbar(self, orient=tk.HORIZONTAL)
hscrollbar.grid(column=0, row=1, columnspan=5, sticky=tk.E+tk.W)
self.canvas.config(xscrollcommand=hscrollbar.set)
hscrollbar.config(command=self.canvas.xview)
button_frame = tk.Frame(self)
button_frame.grid(column=0, row=3, columnspan=2)
for column, (text, command, var)in enumerate(
(("||", self.start_stop, None),
("+", self.microscope.zoom_image, zoom_step),
("-", self.microscope.zoom_image, -zoom_step),
("+/-", self.reset_zoom, None),
("[ ]", self.take_picture, None),
("REC", self.recording_film, None),
(self.microscope.interpolation, self.set_interpolation_method
, None))):
button = tk.Button(button_frame, text=text, width=7,
relief="raised", font="Arial 10 bold")
button.grid(column=column, row=0)
self.buttons.append(button)
if var:
button.config(command=partial(command, var))
else:
button.config(command=command)
self.buttons[5].config(bg = "lightgreen")

def start_stop(self):
if self.after_id is None:
self.buttons[0].config(text = "||")
self.run()
else:
self.buttons[0].config(text = ">")
self.after_cancel(self.after_id)
self.after_id = None

def run(self):
self.tk_image = self.microscope.get_image()
if self.tk_image:
self.canvas.delete("img")
self.tk_image = ImageTk.PhotoImage(self.tk_image)
self.canvas.create_image((0,0), anchor=tk.NW,
image=self.tk_image, tag="img")
width, height = self.microscope.zoom_size
self.canvas.config(scrollregion = (0, 0, width, height))
self.after_id = self.after(self.UPDATE_INTERVAL, self.run)
else:
self.raise_cam_id_error()

def raise_cam_id_error(self):
self.canvas.create_text((self.width / 2, self.height / 2),
text='NO CAM', font='Arial 40')
for button in self.buttons:
button.config(state="disabled")

def reset_zoom(self):
self.microscope.reset_zoom()

def set_interpolation_method(self):
self.microscope.set_interpolation_method()
self.buttons[-1].config(text = self.microscope.interpolation)



def recording_film(self):
if self.buttons[5].config("bg")[-1] == "lightgreen":
self.buttons[5].config(bg = "red")
self.microscope.recording_start_stop(True,
"{0}{1:%d%b%Y_%H_%M_%S.%f}.avi".format(self.video_path,
datetime.datetime.utcnow()))
else:
self.buttons[5].config(bg = "lightgreen")
self.microscope.recording_start_stop(False)

def take_picture(self):
self.microscope.take_picture("{0}{1:%d%b%Y_%H_%M_%S.%f}.tiff"
.format(self.picture_path, datetime.datetime.utcnow()))

def release(self):
self.parent.destroy()

def main():
root = tk.Tk()
root.title('MICROSCOPE')
try:
with Microscope(DEFAULT_CAM_ID) as microscope:
microscope_ui = MicroscopeUI(
root, microscope, WIDTH, HEIGHT)
microscope_ui.pack(expand=tk.YES)
microscope_ui.run()
root.protocol("WM_DELETE_WINDOW", microscope_ui.release)
root.mainloop()

except RuntimeError:
tk.Label(root, text = 'can not open camera {0!r}'.format(
DEFAULT_CAM_ID), font = "Arial 20", height = 10).pack()
root.mainloop()

if __name__ == '__main__':
main()
pack(expand=tk.YES)
microscope_ui.run()
root.protocol("WM_DELETE_WINDOW", microscope_ui.release)
root.mainloop()

except RuntimeError:
tk.Label(root, text = 'can not open camera {0!r}'.format(
DEFAULT_CAM_ID), font = "Arial 20", height = 10).pack()
root.mainloop()

if __name__ == '__main__':
main()[/code]

Gruß Frank
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo,
hier sind einige Aufnahme mit der Software und die Beobachtungen des Mondes werde ich noch weiterführen. Das Teleskop hat 12,49€ gekostet und das Mikroskop gibt es für 11€ bei eBay.

https://m.youtube.com/channel/UCFFMvm-lul72XVpl0kNMhlA

Gruß Frank
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo,

für die Aufnahmen von Himmelskörpern ist die Serienbildaufnahme von Vorteil, da Luftunruhen die Bilder unscharf machen. Mit mehreren Bilder ist die Chance von guten Aufnahmen erhöht. Diese Funktion und die Möglichkeit den Zeitabstand zwischen den Bilder zu verändern ist jetzt auch vorhanden. Bei Serienbildaufnahmen wird immer ein neuer Ordner mit Zeitangabe angelegt. Die Buttons erklären sich selbst und Veränderungen werden auf dem Bildschirm angezeigt. Diese können mit dem ON/OFF-Button an/abgeschaltet werden.

Code: Alles auswählen

#! /usr/bin/env python
# -*- coding: utf-8
from __future__ import division

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk
import os
import datetime
import cv2
from PIL import Image, ImageTk
from functools import partial
from itertools import cycle

WIDTH = 640
HEIGHT = 480
VIDEO_CODEC = "XVID"
DEFAULT_CAM_ID = 0


class Microscope(object):

    PROPID_WIDTH = 3
    PROPID_HEIGHT = 4

    def __init__(self, cam_id = id, number_of_imgs = 10, 
        series_time_interval = 1, image_path = "", video_path = "",
        series_img_path = ""): 
        self.cam = cv2.VideoCapture(cam_id)
        self.recording = False
        if not self.cam.isOpened():
            raise RuntimeError("can not open camera {0!r}".format(
                cam_id))        
        self.number_of_imgs = number_of_imgs
        self.series_counter = None
        self.series_time_counter = None
        self.series_time_interval = series_time_interval
        self.image_path = image_path
        self.video_path = video_path
        self.series_img_path = series_img_path
        self.series_dir = None
        self.interpolation_methods = {"NEAREST" : cv2.INTER_NEAREST,
                                      "LINEAR" : cv2.INTER_LINEAR,
                                      "AREA" : cv2.INTER_AREA,
                                      "CUBIC" : cv2.INTER_CUBIC,
                                      "LANCZOS4" : cv2.INTER_LANCZOS4}
        self.interpolation_methods_keys = cycle(self.interpolation_methods.keys())
        self.interpolation_method = self.interpolation_methods_keys.next()
        self.cam_width = self.zoom_width = self.cam.get(self.PROPID_WIDTH)
        self.cam_height = self.zoom_height = self.cam.get(self.PROPID_HEIGHT)
        self.aspect_ratio = self.zoom_width / self.zoom_height
        self.aspect_ratio = self.zoom_width / self.zoom_height


    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.release()

    @property
    def size(self):
        return (int(self.cam_width), int(self.cam_height))
        
    @property
    def zoom_size(self):
        return (int(self.zoom_width), int(self.zoom_height))
        
    @property
    def interpolation(self):
        return self.interpolation_method

    def get_image(self):
        state, frame = self.cam.read()
        
        inter_frame = cv2.resize(frame, self.zoom_size, 
            interpolation = self.interpolation_methods[
            self.interpolation_method])

        if state:
            image = Image.frombytes("RGB",  self.zoom_size, inter_frame,
                "raw", "BGR")
            if self.series_counter:
                if self.series_time_counter.next() == \
                    self.series_time_interval:
                    self.series_time_counter = (x for x in xrange(
                        self.series_time_interval +1))
                    counter = self.series_counter.next()
                    if counter != self.number_of_imgs:
                        self.take_picture("{0}{1}/{2}.tiff".format(
                            self.series_dir, self.series_img_path, 
                            counter + 1))
                    else:
                        self.series_counter = None
            if self.recording:
                self.video_writer.write(inter_frame)
                    
            return image
        else:
            return state

    def recording_start_stop(self, name = None):
        if name:
            name = name
        else:
            name = "{0:%d%b%Y_%H_%M_%S.%f}.avi".format(
                datetime.datetime.utcnow())
        if self.recording:
            self.recording = False
        else:
            self.recording = True
            self.video_writer = cv2.VideoWriter("{0}{1}".format(
                self.video_path, name), cv2.cv.CV_FOURCC(* VIDEO_CODEC),
                24, self.zoom_size)
                
    def take_series_picture(self):
        if self.series_counter == None:         
            self.series_dir = "{0:%d%b%Y_%H_%M_%S.%f}".format(
                datetime.datetime.utcnow())
            os.makedirs(self.series_dir)
            self.series_time_counter = (x for x in xrange(
                self.series_time_interval + 1))
            self.series_counter = (x for x in xrange(self.number_of_imgs +1))

    def take_picture(self, name = None):
        if name:
            name = name
        else:
            name = "{0:%d%b%Y_%H_%M_%S.%f}.tiff".format(
                datetime.datetime.utcnow())
                
        self.get_image().save("{0}{1}".format(self.image_path, name))
        
    def series_up_down(self, step):
        if self.series_counter == None:
            if self.number_of_imgs > 1 or step > 0:
                self.number_of_imgs += step
        
    def set_time_interval(self, step):
        if self.series_counter == None:
            if self.series_time_interval > 1 or step > 0:
                self.series_time_interval += step
        
    def zoom_image(self, step):
        width = int(self.zoom_width + step * self.aspect_ratio)
        height = int(self.zoom_height + step)
        if width > 0 and height > 0:
            self.zoom_width = width
            self.zoom_height = height

    def reset_zoom(self):
        self.zoom_width, self.zoom_height = self.size
        
    def set_interpolation_method(self):
        self.interpolation_method = self.interpolation_methods_keys.next()
        
    def release(self):
        self.cam.release()

class MicroscopeUI(tk.Frame):

    UPDATE_INTERVAL = 20
    REC_ON = 3
    
    def __init__(self, parent, microscope, width, height, zoom_step = 10):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.width = width
        self.height = height
        self.tk_image = None
        self.rec_on = (x for x in xrange(self.REC_ON))
        self.buttons = list()
        self.recording = False
        self.text_on = False
        self.microscope = microscope
        self.canvas = tk.Canvas(self, width=width, height=height)
        self.canvas.grid(column=0, row=0)
        vscrollbar = tk.Scrollbar(self)
        vscrollbar.grid(column=1, row=0, sticky=tk.N+tk.S)
        self.canvas.config(yscrollcommand=vscrollbar.set)
        vscrollbar.config(command=self.canvas.yview)
        hscrollbar = tk.Scrollbar(self, orient=tk.HORIZONTAL)
        hscrollbar.grid(column=0, row=1, columnspan=5, sticky=tk.E+tk.W)
        self.canvas.config(xscrollcommand=hscrollbar.set)
        hscrollbar.config(command=self.canvas.xview)
        button_frame = tk.Frame(self)
        button_frame.grid(column=0, row=3, columnspan=2)
        for column, (text, width, command, var)in enumerate(
            (("||", 2, self.start_stop, None),
             ("Z+", 2, self.microscope.zoom_image, zoom_step),
             ("Z-", 2, self.microscope.zoom_image, -zoom_step),
             ("Z+/-", 2, self.microscope.reset_zoom, None),
             ("[1]", 2, self.take_picture, None),
             ("REC", 2, self.recording_film, None),
             ("INTPOL", 5, self.set_interpolation_method, None),
             ("[S]]]", 2, self.take_series_pictures, None),
             ("S+", 2, self.series_up_down, 1),
             ("S-", 2, self.series_up_down, -1),
             ("T+", 2, self.time_interval_up_down, 1),
             ("T-", 2, self.time_interval_up_down, -1),
             ("ON", 2, self.text_on_off, None))):
            button = tk.Button(button_frame, text=text, width=width,
                relief="raised", font="Arial 10 bold")
            button.grid(column=column, row=0)
            self.buttons.append(button)
            if var:
                 button.config(command=partial(command, var))
            else:
                 button.config(command=command)

    def start_stop(self):
        if self.after_id is None:
            self.buttons[0].config(text = "||")
            self.run()
        else:
            self.buttons[0].config(text = ">")
            self.after_cancel(self.after_id)
            self.after_id = None

    def run(self):
        self.tk_image = self.microscope.get_image()
        if self.tk_image:
            self.canvas.delete("img", "rec", "txt")
            self.tk_image = ImageTk.PhotoImage(self.tk_image)
            self.canvas.create_image((0,0), anchor=tk.NW,
                image=self.tk_image, tag="img")
            if self.recording:
                if self.rec_on.next() == self.REC_ON -1:
                    self.rec_on = (x for x in xrange(self.REC_ON))
                    self.canvas.create_oval(10, 10, 25, 25, fill="red", 
                        tag="rec")
            if self.text_on:
                self.buttons[12].config(text = "OFF")
                self.canvas.create_text(self.width / 2, self.height / 2, 
                    text="+", font="Arial 30", tag = "txt")
                self.canvas.create_text(30, 10, 
                text = "FILTER: {0}  SERIES: {1}   TIME: {2}".format(
                    self.microscope.interpolation, 
                    self.microscope.number_of_imgs, 
                    self.microscope.series_time_interval), anchor = "nw",
                    tag = "txt")
            else:
                self.buttons[12].config(text = "ON")
                
            width, height = self.microscope.zoom_size
            self.canvas.config(scrollregion = (0, 0, width, height))
            self.after_id = self.after(self.UPDATE_INTERVAL, self.run)
        else:
            self.raise_cam_id_error()
            
    def text_on_off(self):
        if self.text_on:
            self.text_on = False
        else:
            self.text_on = True
                
    def raise_cam_id_error(self):
        self.canvas.create_text((self.width / 2, self.height / 2),
            text='NO CAM', font='Arial 40')
        for button in self.buttons:
            button.config(state="disabled")
            
    def series_up_down(self, step):
        if self.text_on == False:
            self.text_on_off()
        self.microscope.series_up_down(step)
        
    def time_interval_up_down(self, step):
        if self.text_on == False:
            self.text_on_off()
        self.microscope.set_time_interval(step)
        
    def set_interpolation_method(self):
        if self.text_on == False:
            self.text_on_off()
        self.microscope.set_interpolation_method()
        
    def recording_film(self):
        if self.recording:
            self.microscope.recording_start_stop()
            self.recording = False
        else:
            self.microscope.recording_start_stop()
            self.recording = True
            
    def take_series_pictures(self):
        self.microscope.take_series_picture()

    def take_picture(self):
        self.microscope.take_picture()

    def release(self):
        self.parent.destroy()

def main():
    root = tk.Tk()
    root.title('MICROSCOPE')
    
    try:
        with Microscope(DEFAULT_CAM_ID) as microscope:
            def take_picture(e):
                microscope_ui.take_picture()
            microscope_ui = MicroscopeUI(
                root, microscope, WIDTH, HEIGHT)
            microscope_ui.pack(expand=tk.YES)
            microscope_ui.run()
            root.protocol("WM_DELETE_WINDOW", microscope_ui.release)
            #root.bind("<Key" + chr(10) + ">", take_picture)
            
            root.mainloop()
    except RuntimeError:
        tk.Label(root, text = 'can not open camera {0!r}'.format(
                DEFAULT_CAM_ID), font = "Arial 20", height = 10).pack()
        root.mainloop()
    
if __name__ == '__main__':
    main()
Gruß Frank
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo,

die Textfarbe war nur in schwarz und bei Beobachtungen am Nachthimmel eher unbrauchbar. Mit der Taste "C" lässt sich die Farbe ändern.

Code: Alles auswählen

#! /usr/bin/env python
# -*- coding: utf-8
from __future__ import division

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk
import os
import datetime
import cv2
from PIL import Image, ImageTk
from functools import partial
from itertools import cycle

WIDTH = 640
HEIGHT = 480
VIDEO_CODEC = "XVID"
DEFAULT_CAM_ID = 0


class Microscope(object):

    PROPID_WIDTH = 3
    PROPID_HEIGHT = 4

    def __init__(self, cam_id = id, number_of_imgs = 10, 
        series_time_interval = 1, image_path = "", video_path = "",
        series_img_path = ""): 
        self.cam = cv2.VideoCapture(cam_id)
        self.recording = False
        if not self.cam.isOpened():
            raise RuntimeError("can not open camera {0!r}".format(
                cam_id))        
        self.number_of_imgs = number_of_imgs
        self.series_counter = None
        self.series_time_counter = None
        self.series_time_interval = series_time_interval
        self.image_path = image_path
        self.video_path = video_path
        self.series_img_path = series_img_path
        self.series_dir = None
        self.interpolation_methods = {"NEAREST" : cv2.INTER_NEAREST,
                                      "LINEAR" : cv2.INTER_LINEAR,
                                      "AREA" : cv2.INTER_AREA,
                                      "CUBIC" : cv2.INTER_CUBIC,
                                      "LANCZOS4" : cv2.INTER_LANCZOS4}
        self.interpolation_methods_keys = cycle(self.interpolation_methods.keys())
        self.interpolation_method = self.interpolation_methods_keys.next()
        self.cam_width = self.zoom_width = self.cam.get(self.PROPID_WIDTH)
        self.cam_height = self.zoom_height = self.cam.get(self.PROPID_HEIGHT)
        self.aspect_ratio = self.zoom_width / self.zoom_height
        self.aspect_ratio = self.zoom_width / self.zoom_height


    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.release()

    @property
    def size(self):
        return (int(self.cam_width), int(self.cam_height))
        
    @property
    def zoom_size(self):
        return (int(self.zoom_width), int(self.zoom_height))
        
    @property
    def interpolation(self):
        return self.interpolation_method

    def get_image(self):
        state, frame = self.cam.read()
        
        inter_frame = cv2.resize(frame, self.zoom_size, 
            interpolation = self.interpolation_methods[
            self.interpolation_method])

        if state:
            image = Image.frombytes("RGB",  self.zoom_size, inter_frame,
                "raw", "BGR")
            if self.series_counter:
                if self.series_time_counter.next() == \
                    self.series_time_interval:
                    self.series_time_counter = (x for x in xrange(
                        self.series_time_interval +1))
                    counter = self.series_counter.next()
                    if counter != self.number_of_imgs:
                        self.take_picture("{0}{1}/{2}.tiff".format(
                            self.series_dir, self.series_img_path, 
                            counter + 1))
                    else:
                        self.series_counter = None
            if self.recording:
                self.video_writer.write(inter_frame)
                    
            return image
        else:
            return state

    def recording_start_stop(self, name = None):
        if name:
            name = name
        else:
            name = "{0:%d%b%Y_%H_%M_%S.%f}.avi".format(
                datetime.datetime.utcnow())
        if self.recording:
            self.recording = False
        else:
            self.recording = True
            self.video_writer = cv2.VideoWriter("{0}{1}".format(
                self.video_path, name), cv2.cv.CV_FOURCC(* VIDEO_CODEC),
                24, self.zoom_size)
                
    def take_series_picture(self):
        if self.series_counter == None:         
            self.series_dir = "{0:%d%b%Y_%H_%M_%S.%f}".format(
                datetime.datetime.utcnow())
            os.makedirs(self.series_dir)
            self.series_time_counter = (x for x in xrange(
                self.series_time_interval + 1))
            self.series_counter = (x for x in xrange(self.number_of_imgs +1))

    def take_picture(self, name = None):
        if name:
            name = name
        else:
            name = "{0:%d%b%Y_%H_%M_%S.%f}.tiff".format(
                datetime.datetime.utcnow())
                
        self.get_image().save("{0}{1}".format(self.image_path, name))
        
    def series_up_down(self, step):
        if self.series_counter == None:
            if self.number_of_imgs > 1 or step > 0:
                self.number_of_imgs += step
        
    def set_time_interval(self, step):
        if self.series_counter == None:
            if self.series_time_interval > 1 or step > 0:
                self.series_time_interval += step
        
    def zoom_image(self, step):
        width = int(self.zoom_width + step * self.aspect_ratio)
        height = int(self.zoom_height + step)
        if width > 0 and height > 0:
            self.zoom_width = width
            self.zoom_height = height

    def reset_zoom(self):
        self.zoom_width, self.zoom_height = self.size
        
    def set_interpolation_method(self):
        self.interpolation_method = self.interpolation_methods_keys.next()
        
    def release(self):
        self.cam.release()

class MicroscopeUI(tk.Frame):

    UPDATE_INTERVAL = 20
    REC_ON = 3
    
    def __init__(self, parent, microscope, width, height, zoom_step = 10):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.width = width
        self.height = height
        self.tk_image = None
        self.rec_on = (x for x in xrange(self.REC_ON))
        self.buttons = list()
        self.recording = False
        self.text_on = False
        self.microscope = microscope
        self.text_colour = ["white", "green", "black", "red", "magenta", 
            "green", "brown", "yellow", "blue", "orange", "gray"]
        self.text_colour_index = cycle(self.text_colour)
        self.text_colour = self.text_colour_index.next()
        self.canvas = tk.Canvas(self, width=width, height=height)
        self.canvas.grid(column=0, row=0)
        vscrollbar = tk.Scrollbar(self)
        vscrollbar.grid(column=1, row=0, sticky=tk.N+tk.S)
        self.canvas.config(yscrollcommand=vscrollbar.set)
        vscrollbar.config(command=self.canvas.yview)
        hscrollbar = tk.Scrollbar(self, orient=tk.HORIZONTAL)
        hscrollbar.grid(column=0, row=1, columnspan=5, sticky=tk.E+tk.W)
        self.canvas.config(xscrollcommand=hscrollbar.set)
        hscrollbar.config(command=self.canvas.xview)
        button_frame = tk.Frame(self)
        button_frame.grid(column=0, row=3, columnspan=2)
        for column, (text, width, command, var)in enumerate(
            (("||", 2, self.start_stop, None),
             ("Z+", 2, self.microscope.zoom_image, zoom_step),
             ("Z-", 2, self.microscope.zoom_image, -zoom_step),
             ("Z+/-", 2, self.microscope.reset_zoom, None),
             ("[1]", 2, self.take_picture, None),
             ("REC", 2, self.recording_film, None),
             ("INTPOL", 5, self.set_interpolation_method, None),
             ("[S]]]", 2, self.take_series_pictures, None),
             ("S+", 2, self.series_up_down, 1),
             ("S-", 2, self.series_up_down, -1),
             ("T+", 2, self.time_interval_up_down, 1),
             ("T-", 2, self.time_interval_up_down, -1),
             ("ON", 2, self.text_on_off, None),
             ("C", 2, self.change_text_colour, None))):
            button = tk.Button(button_frame, text=text, width=width,
                relief="raised", font="Arial 10 bold")
            button.grid(column=column, row=0)
            self.buttons.append(button)
            if var:
                 button.config(command=partial(command, var))
            else:
                 button.config(command=command)

    def start_stop(self):
        if self.after_id is None:
            self.buttons[0].config(text = "||")
            self.run()
        else:
            self.buttons[0].config(text = ">")
            self.after_cancel(self.after_id)
            self.after_id = None

    def run(self):
        self.tk_image = self.microscope.get_image()
        if self.tk_image:
            self.canvas.delete("img", "rec", "txt")
            self.tk_image = ImageTk.PhotoImage(self.tk_image)
            self.canvas.create_image((0,0), anchor=tk.NW,
                image=self.tk_image, tag="img")
            if self.recording:
                if self.rec_on.next() == self.REC_ON -1:
                    self.rec_on = (x for x in xrange(self.REC_ON))
                    self.canvas.create_oval(10, 10, 25, 25, fill="red", 
                        tag="rec")
            if self.text_on:
                self.buttons[12].config(text = "OFF")
                self.canvas.create_text(self.width / 2, self.height / 2, 
                    text="+", font="Arial 30", fill=self.text_colour, tag="txt")
                self.canvas.create_text(30, 10, font="Arial 11 bold", 
                text = "FILTER: {0}  SERIES: {1}   TIME: {2}".format(
                    self.microscope.interpolation, 
                    self.microscope.number_of_imgs, 
                    self.microscope.series_time_interval), anchor="nw", 
                        fill = self.text_colour, tag = "txt")
            else: 
                self.buttons[12].config(text = "ON")
                
            width, height = self.microscope.zoom_size
            self.canvas.config(scrollregion = (0, 0, width, height))
            self.after_id = self.after(self.UPDATE_INTERVAL, self.run)
        else:
            self.raise_cam_id_error()
            
    def change_text_colour(self):       
        if self.text_on == False:
            self.text_on_off()
        self.text_colour = self.text_colour_index.next()
        
    def text_on_off(self):
        if self.text_on:
            self.text_on = False
        else:
            self.text_on = True
                
    def raise_cam_id_error(self):
        self.canvas.create_text((self.width / 2, self.height / 2),
            text='NO CAM', font='Arial 40')
        for button in self.buttons:
            button.config(state="disabled")
            
    def series_up_down(self, step):
        if self.text_on == False:
            self.text_on_off()
        self.microscope.series_up_down(step)
        
    def time_interval_up_down(self, step):
        if self.text_on == False:
            self.text_on_off()
        self.microscope.set_time_interval(step)
        
    def set_interpolation_method(self):
        if self.text_on == False:
            self.text_on_off()
        self.microscope.set_interpolation_method()
        
    def recording_film(self):
        if self.recording:
            self.microscope.recording_start_stop()
            self.recording = False
        else:
            self.microscope.recording_start_stop()
            self.recording = True
            
    def take_series_pictures(self):
        self.microscope.take_series_picture()

    def take_picture(self):
        self.microscope.take_picture()

    def release(self):
        self.parent.destroy()

def main():
    root = tk.Tk()
    root.title('MICROSCOPE')
    
    try:
        with Microscope(DEFAULT_CAM_ID) as microscope:
            def take_picture(e):
                microscope_ui.take_picture()
            microscope_ui = MicroscopeUI(
                root, microscope, WIDTH, HEIGHT)
            microscope_ui.pack(expand=tk.YES)
            microscope_ui.run()
            root.protocol("WM_DELETE_WINDOW", microscope_ui.release)
            #root.bind("<Key" + chr(10) + ">", take_picture)
            
            root.mainloop()
    except RuntimeError:
        tk.Label(root, text = 'can not open camera {0!r}'.format(
                DEFAULT_CAM_ID), font = "Arial 20", height = 10).pack()
        root.mainloop()
    
if __name__ == '__main__':
    main()
Gruß Frank
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@kaytec: hier ein paar Anmerkungen zum Code

Zeile 6: Du benutzt hier eine Fallunterscheidung zwischen Python2 und 3, später aber xrange, was nur in Python2 existiert.
Zeile 27: um die Gleichheitszeichen von Defaultargumenten macht man keine Leerzeichen. Der Defaultwert von `cam_id` `id` ist quatsch, wahrscheinlich wolltest Du DEFAULT_CAM_ID verwenden. Die Defaultwerte der Pfade sollten "." sein, also aktuelles Verzeichnis.
Zeile 43: interpolation_methods sollte besser eine Klassenkonstante sein.
Zeile 52/53: doppelte Zeile
Zeile 64/68: die Werte sind schon Integer, eine Umwandlung also überflüssig
Zeile 77: wenn ein Fehler auftritt, ist frame ungültig, das muß also in den if-Block.
Zeile 81/101: Wenn ein Fehler auftritt, sollte eine Exception geworfen werden und nicht ein ungültiger Wert zurückgeliefert werden. Später erwartest Du, dass get_image ein Bild liefert, was dann zu Folgefehlern führt.
Zeile 85: statt mit Iteratoren zu spielen, solltest Du einfach einen Zähler von series_time_interval bis 0 zählen lassen.
Zeile 90: ebenso hier, entweder Du nimmst einen Iterator und fängst StopIteration ab, um zu wissen, wann Du fertig bist, oder Du zählst mit einem einfachen Zähler.
Zeile 91: Pfade setzt man mit os.path.join zusammen, nicht mit format.
Zeile 103: start und stop tun zwei völlig verschiedene Dinge, mach zwei Methoden draus.
Zeile 104f: der if-Block tut nichts und kann weg. Besser `if not name:`
Zeile 107: Datumsdateinamen am besten im Format YYYYMMDD, dann kann man sie lexikalisch sortieren.
Zeile 118: Funktionen, die einfach nichts machen, wenn etwas nicht stimmt, sind komisch. Besser Fehlermeldung
Zeile 137: dieses komische If-Konstrukt verhindert nicht, dass number_of_imgs negativ werden kann. Mach es explizit, z.b. mit max(number_of_imgs + step, 1)
Zeile 146: bei mehrfachem Zoomen wird width irgendwann durch Rundungsfehler ungenau, besser jedesmal aus aspect_ratio und height berechnen.
Zeile 155: set_interpolation_method sollte besser next_interpolation_method heißen.

Zeile 173: Variablen sollten erst dann initialisiert werden, wenn sie gebraucht werden und nicht 20 Zeilen davor.
Zeile 177: text_colour wird zwei Zeilen später überschrieben, sollte also hier kein Attribut sein
Zeile 121: var sollte eine Liste der Argumente sein, denn None/False/0/etc. kann ja durchaus ein gültiger Parameterwert sein. Dann entfällt auch die Fallunterscheidung
Zeile 219,222,239,249: magische Werte vermeiden, buttons könnte ein Wörterbuch sein.
Zeile 227: tk_image wird drei Zeilen später überschrieben, sollte hier also kein Attribut sein.
Zeile 228: Fehlerbehandlung wurde ja schon bei get_image angesprochen
Zeile 239/249: sollte in text_on_off gemacht werden
Zeile 258f: diese zwei Zeilen kommen so oft vor, dass sie in eine eigene Methode wandern sollten.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo Sirius3,

danke für die konstruktive Kritik - habe deine Anmerkungen gelesen und versuche sie heute Abend umzusetzten.

Gruß Frank
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo Sirius3,

Zeile 64-68: size geht - bei zoom_size gibt es eine Fehlermeldung:

Code: Alles auswählen

Traceback (most recent call last):
  File "microscope.py", line 322, in <module>
    main()
  File "microscope.py", line 311, in main
    microscope_ui.run()
  File "microscope.py", line 220, in run
    tk_image = self.microscope.get_image()
  File "microscope.py", line 79, in get_image
    self.interpolation_method])
TypeError: integer argument expected, got float
>Exit code: 1
mit print gibt es diese Ausgabe --> (640.0, 480.0

Zeile 85/90: habe jetz noch cycle eingebastelt und verhindere so eine Stopiteration. Macht es bestimmt nicht besser, doch warum sollte man es nicht so machen ?

Zeile 107:Mit deinem Datumsformat hat es nicht so geklappt, doch mir waren eigntlich nur die Eindeutigkeit wichtig (Ms). Die Datumsangab/Zeit ist eher zweitranging.

Zeile 118: Die Funktion macht schon was, doch soll sie erst wieder eine Funktion haben, wenn der vorhergehende Start abgelaufen ist. Ich könnte auch den Button blockieren - ist aber mehr Aufwand.

Zeile 137: Das tut schon was es soll und es ist auch ok die Anzahl 10 zu unterschreiten, doch < 1 ist dumm. Hier verhindere ich auch das Starten vor dem Ablauf des vorgehenden Starten.

Zeile 121: In meinem Fall ist es doch so gewollt und ich wollte nicht zwei Buttonlisten ?!

Zeile 219-249: Wie soll ich sonst den Text der Buttons ändern ?

Zeile 258: Dies sollen eigentlich nur die Bedienung vereinfachen:

Code: Alles auswählen

        if self.text_on_off == False:
            self.text_on_off_off()
Die Funktionen sind schon eigenständig, doch bei Veränderungen soll der Displaytext angeschaltet werden.

Das Konstrukt mit if state und Fehlermeldungen stammt von einer anderen Version, die auf eine Fehlfunktion von OpenCV reagiert. State gibt eigentlich True/False zurück, doch beim Abziehen des Steckers (im Betrieb) gibt es kein False zurück. Sollte der Fehler behoben werden, dann würde es funktionieren. Ist noch von einem Beitrag mit BlackJack - kann es gerne raussuchen. ich kann natürlich auch völlig falsch liegen, denn ein Hilfsprogrammierer ist da nicht so in der Materie drin.

Gruß Frank
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo,

hier ist die nächste Version:

Code: Alles auswählen

#! /usr/bin/env python
# -*- coding: utf-8
from __future__ import division

import tkinter as tk
import os
import datetime
import cv2
from PIL import Image, ImageTk
from functools import partial
from itertools import cycle

WIDTH = 640
HEIGHT = 480
VIDEO_CODEC = "XVID"
DEFAULT_CAM_ID = -1


class Microscope(object):

    PROPID_WIDTH = 3
    PROPID_HEIGHT = 4
    INTERPOLATION_METHODS= {"NEAREST" : cv2.INTER_NEAREST,
                             "LINEAR" : cv2.INTER_LINEAR,
                             "AREA" : cv2.INTER_AREA,
                             "CUBIC" : cv2.INTER_CUBIC,
                             "LANCZOS4" : cv2.INTER_LANCZOS4}

    def __init__(self, cam_id=DEFAULT_CAM_ID, number_of_imgs=10, 
        series_time_interval=1, image_path=".", video_path=".",
        series_img_path=".", img_format=".tiff"):
        self.cam = cv2.VideoCapture(cam_id)
        if not self.cam.isOpened():
            raise RuntimeError("can not open camera {0!r}".format(
                cam_id))
        self.img_format = img_format
        self.take_series = False
        self.recording = False
        self.number_of_imgs = number_of_imgs
        self.series_counter = None
        self.series_time_counter = None
        self.series_time_interval = series_time_interval
        self.image_path = image_path
        self.video_path = video_path
        self.series_img_path = series_img_path
        self.series_dir = None
        self.interpolation_methods = self.INTERPOLATION_METHODS
        self.interpolation_methods_keys = cycle(
            self.interpolation_methods.keys())
        self.interpolation_method = self.interpolation_methods_keys.next()
        self.cam_width = self.zoom_width = self.cam.get(self.PROPID_WIDTH)
        self.cam_height = self.zoom_height = self.cam.get(self.PROPID_HEIGHT)
        

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.release()

    @property
    def size(self):
        return self.cam_width, self.cam_height
        
    @property
    def zoom_size(self):
        return int(self.zoom_width), int(self.zoom_height)
        
    @property
    def interpolation(self):
        return self.interpolation_method

    def get_image(self):
        state, frame = self.cam.read()

        if state:        
            inter_frame = cv2.resize(frame, self.zoom_size, 
            interpolation = self.interpolation_methods[
            self.interpolation_method])
            
            image = Image.frombytes("RGB",  self.zoom_size, inter_frame,
                "raw", "BGR")
            if self.take_series:
                if self.series_time_counter.next() == \
                    self.series_time_interval:
                    counter = self.series_counter.next()
                    if counter != self.number_of_imgs:
                        img_name = "{0}{1}".format(counter +1, self.img_format)
                        self.get_image().save(os.path.join(self.series_img_path, 
                            self.series_dir, img_name))
                    else:
                        self.take_series = False
            if self.recording:
                self.video_writer.write(inter_frame)
                    
            return image
        else:
            return state

    def recording_start_stop(self, name = None):
        if not name:
            name = "{0:%d%b%Y_%H_%M_%S.%f}.avi".format(
                datetime.datetime.utcnow())
        if self.recording:
            self.recording = False
        else:
            self.recording = True
            self.video_writer = cv2.VideoWriter(os.path.join(
                self.video_path, name), cv2.cv.CV_FOURCC(* VIDEO_CODEC),
                24, self.zoom_size)
                
    def take_series_picture(self):
        if self.take_series == False:         
            self.series_dir = "{0:%d%b%Y_%H_%M_%S.%f}".format(
                datetime.datetime.utcnow())
            os.makedirs(self.series_dir)
            self.series_time_counter = cycle(x for x in xrange(
                self.series_time_interval +1))
            self.series_counter = cycle(x for x in xrange(
                self.number_of_imgs +1))
            self.take_series = True

    def take_picture(self, name = None):
        if not name:
            name = "{0:%d%b%Y_%H_%M_%S.%f}{1}".format(
                datetime.datetime.utcnow(), self.img_format)
        self.get_image().save(os.path.join(self.image_path, name))
        
    def series_up_down(self, step):
        if self.take_series == False: 
            if self.number_of_imgs > 1 or step > 0:
                self.number_of_imgs += step
        
    def set_time_interval(self, step):
        if self.take_series == False:
            if self.series_time_interval > 1 or step > 0:
                self.series_time_interval += step
        
    def zoom_image(self, step):
        width = int(self.zoom_width + step * self.zoom_width / self.zoom_height)
        height = int(self.zoom_height + step)
        if width > 0 and height > 0:
            self.zoom_width = width
            self.zoom_height = height

    def reset_zoom(self):
        self.zoom_width, self.zoom_height = self.size
        
    def next_interpolation_method(self):
        self.interpolation_method = self.interpolation_methods_keys.next()
        
    def release(self):
        self.cam.release()

class MicroscopeUI(tk.Frame):

    UPDATE_INTERVAL = 20
    REC_ON = 3
    
    def __init__(self, parent, microscope, width, height, zoom_step=10):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.width = width
        self.height = height
        self.tk_image = None
        self.rec_on = cycle(x for x in xrange(self.REC_ON))
        self.recording = False
        self.text_on_off = False
        self.microscope = microscope
        self.text_colour_index = cycle(["white", "green", "black", "red",
            "magenta", "green", "brown", "yellow", "blue", "orange", "gray"])
        self.text_colour = self.text_colour_index.next()
        self.canvas = tk.Canvas(self, width=width, height=height)
        self.canvas.grid(column=0, row=0)
        vscrollbar = tk.Scrollbar(self)
        vscrollbar.grid(column=1, row=0, sticky=tk.N+tk.S)
        self.canvas.config(yscrollcommand=vscrollbar.set)
        vscrollbar.config(command=self.canvas.yview)
        hscrollbar = tk.Scrollbar(self, orient=tk.HORIZONTAL)
        hscrollbar.grid(column=0, row=1, columnspan=5, sticky=tk.E+tk.W)
        self.canvas.config(xscrollcommand=hscrollbar.set)
        hscrollbar.config(command=self.canvas.xview)
        button_frame = tk.Frame(self)
        button_frame.grid(column=0, row=3, columnspan=2)
        self.buttons = list()
        for column, (text, width, command, var)in enumerate(
            (("||", 2, self.start_stop, None),
             ("Z+", 2, self.microscope.zoom_image, zoom_step),
             ("Z-", 2, self.microscope.zoom_image, -zoom_step),
             ("Z+/-", 2, self.microscope.reset_zoom, None),
             ("[1]", 2, self.take_picture, None),
             ("REC", 2, self.recording_film, None),
             ("INTPOL", 5, self.next_interpolation_method, None),
             ("[S]]]", 2, self.take_series_pictures, None),
             ("S+", 2, self.series_up_down, 1),
             ("S-", 2, self.series_up_down, -1),
             ("T+", 2, self.time_interval_up_down, 1),
             ("T-", 2, self.time_interval_up_down, -1),
             ("ON", 2, self.text_on_off_off, None),
             ("C", 2, self.change_text_colour, None))):
            button = tk.Button(button_frame, text=text, width=width,
                relief="raised", font="Arial 10 bold")
            button.grid(column=column, row=0)
            self.buttons.append(button)
            if var:
                 button.config(command=partial(command, var))
            else:
                 button.config(command=command)

    def start_stop(self):
        if self.after_id is None:
            self.buttons[0].config(text = "||")
            self.run()
        else:
            self.buttons[0].config(text = ">")
            self.after_cancel(self.after_id)
            self.after_id = None

    def run(self):
        tk_image = self.microscope.get_image()
        if tk_image:
            self.canvas.delete("img", "rec", "txt")
            self.tk_image = ImageTk.PhotoImage(tk_image)
            self.canvas.create_image((0,0), anchor=tk.NW,
                image=self.tk_image, tag="img")
            if self.recording:
                if self.rec_on.next() == 0:
                    self.canvas.create_oval(10, 10, 25, 25, fill="red", 
                        tag="rec")
            if self.text_on_off:
                self.buttons[12].config(text = "OFF")
                self.canvas.create_text(self.width / 2, self.height / 2, 
                    text="+", font="Arial 30", fill=self.text_colour, 
                    tag="txt")
                self.canvas.create_text(30, 10, font="Arial 11 bold", 
                text = "FILTER: {0}  SERIES: {1}  TIME: {2}  RES: {3}".format(
                    self.microscope.interpolation, 
                    self.microscope.number_of_imgs,                             
                    self.microscope.series_time_interval,
                    self.microscope.zoom_size), anchor="nw", 
                    fill = self.text_colour, tag = "txt")
            else: 
                self.buttons[12].config(text = "ON")
                
            width, height = self.microscope.zoom_size
            self.canvas.config(scrollregion = (0, 0, width, height))
            self.after_id = self.after(self.UPDATE_INTERVAL, self.run)
        else:
            self.raise_cam_id_error()
            
    def change_text_colour(self):       
        if self.text_on_off == False:
            self.text_on_off_off()
        self.text_colour = self.text_colour_index.next()
        
    def text_on_off_off(self):
        if self.text_on_off:
            self.text_on_off = False
        else:
            self.text_on_off = True
                
    def raise_cam_id_error(self):
        self.canvas.create_text((self.width / 2, self.height / 2),
            text='NO CAM', font='Arial 40')
        for button in self.buttons:
            button.config(state="disabled")
            
    def series_up_down(self, step):
        if self.text_on_off == False:
            self.text_on_off_off()
        self.microscope.series_up_down(step)
        
    def time_interval_up_down(self, step):
        if self.text_on_off == False:
            self.text_on_off_off()
        self.microscope.set_time_interval(step)
        
    def next_interpolation_method(self):
        if self.text_on_off == False:
            self.text_on_off_off()
        self.microscope.next_interpolation_method()
        
    def recording_film(self):
        if self.recording:
            self.microscope.recording_start_stop()
            self.recording = False
        else:
            self.microscope.recording_start_stop()
            self.recording = True
            
    def take_series_pictures(self):
        self.microscope.take_series_picture()

    def take_picture(self):
        self.microscope.take_picture()

    def release(self):
        self.parent.destroy()

def main():
    root = tk.Tk()
    root.title('MICROSCOPE')
    
    try:
        with Microscope(DEFAULT_CAM_ID) as microscope:
            def take_picture(e):
                microscope_ui.take_picture()
            microscope_ui = MicroscopeUI(
                root, microscope, WIDTH, HEIGHT)
            microscope_ui.pack(expand=tk.YES)
            microscope_ui.run()
            root.protocol("WM_DELETE_WINDOW", microscope_ui.release)
            #root.bind("<Key" + chr(10) + ">", take_picture)
            root.mainloop()
            
    except RuntimeError:
        tk.Label(root, text = 'can not open camera {0!r}'.format(
                DEFAULT_CAM_ID), font = "Arial 20", height = 10).pack()
        root.mainloop()
    
if __name__ == '__main__':
    main()
Gruß
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

kaytec hat geschrieben:Zeile 64-68: size geht - bei zoom_size gibt es eine Fehlermeldung
Dann kommt zoom_width/zoom_height noch von woanders her, denn in der Methode `zoom_image` wird explizit nach `int` konvertiert.
kaytec hat geschrieben:Zeile 85/90: habe jetz noch cycle eingebastelt und verhindere so eine Stopiteration. Macht es bestimmt nicht besser, doch warum sollte man es nicht so machen ?
Weil es viel zu umständlich ist, und keiner es so erwarten würde. Ich habe selbst erstmal 5 Minuten gebraucht, um zu verstehen, was da gemacht wird.
Du schreibst etwas, das dem entspricht:

Code: Alles auswählen

number_of_imgs = 5
series_counter = iter(range(number_of_imgs+1))

while True:
    counter = next(series_counter)
    if counter == number_of_imgs:
        break
    print("Bild", counter)
Mach einen Iterator und prüfe ob der Iterator einen bestimmten Wert erreicht hat. Dazu mißbrauche ich aber einen Iterator, denn der weiß selbst, wann er fertig ist. Besser ist also:

Code: Alles auswählen

number_of_imgs = 5
series_counter = iter(range(number_of_imgs))

while True:
    try:
        counter = next(series_counter)
    except StopIteration:
        break
    print("Bild", counter)
Normalerweise benutzt man einfach einen Zähler:

Code: Alles auswählen

number_of_imgs = 5
series_counter = 0

while True:
    if series_counter >= number_of_imgs:
        break
    series_counter += 1
    print("Bild", series_counter)
kaytec hat geschrieben:Zeile 107:Mit deinem Datumsformat hat es nicht so geklappt,
Was soll denn an einem Datumsformat nicht klappen, wenn es Dir doch sowieso nicht auf die genaue Form ankommt?

Code: Alles auswählen

name = "{0:%Y%m%d_%H%M%S.%f}.avi".format(datetime.datetime.utcnow())
In Zeile 118 geht es mir nur darum, dass eine Funktion, die nichts macht den Nutzer verwirrt. Der Vorschlag mit dem Button blockieren ist gut, da hat der Nutzer wenigstens ein Visuelles Feedback.

Und Zeile 137 (jetzt 132) ist immer noch falsch. Wenn self.number_of_imgs 4 ist und step -5 dann ist das Ergebnis -1 ohne dass es Deine if-Abfrage verhindert hätte.

Hier das Beispiel mit dem `var`:

Code: Alles auswählen

        self.buttons = list()
        for column, (text, width, command, var) in enumerate(
            [("||", 2, self.start_stop, ()),
             ("Z+", 2, self.microscope.zoom_image, (zoom_step,)),
             ("Z-", 2, self.microscope.zoom_image, (-zoom_step,)),
             ("Z+/-", 2, self.microscope.reset_zoom, ())),
             ("[1]", 2, self.take_picture, ()),
             ("REC", 2, self.recording_film, ()),
             ("INTPOL", 5, self.next_interpolation_method, ()),
             ("[S]]]", 2, self.take_series_pictures, ()),
             ("S+", 2, self.series_up_down, (1,)),
             ("S-", 2, self.series_up_down, (-1,)),
             ("T+", 2, self.time_interval_up_down, (1,)),
             ("T-", 2, self.time_interval_up_down, (-1,)),
             ("ON", 2, self.text_on_off_off, ()),
             ("C", 2, self.change_text_colour, ())]):
            button = tk.Button(button_frame, text=text, width=width,
                relief="raised", font="Arial 10 bold",
                command=partial(command, *var))
            button.grid(column=column, row=0)
            self.buttons.append(button)
Zeile 219/249: es geht nicht um das wie, sondern wo im Code. An der Stelle gehört das einfach nicht hin. Und wenn man vier mal die selben zwei Zeilen schreibt, ist das ein Grund, das in eine Funktion auszulagern, oder `text_on_off` einen weiteren Parameter zu spendieren:

Code: Alles auswählen

    def set_text_on_off(self, force_on=False):
        self.text_on_off = force_on or not self.text_on_off
        self.buttons[12].config(text="ON" if self.text_on_off else "OFF")

    def change_text_colour(self):      
        self.set_text_on_off(True)
        self.text_colour = self.text_colour_index.next()
Dass die Fehlerbehandlung in `get_image` schwieriger ist, als man sich wünscht, macht doch nur deutlicher, dass man eine ordentliche Fehlerbehandlung braucht, und die besteht eben darin, dass `get_image` nicht einmal ein Image und ein andermal False liefert, sondern immer ein Image und im Fehlerfall eine Exception wirft. So macht man Fehlerbehandlung, und nicht anders.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo Sirius3,

danke für deine ausführliche Kritik.

Die Kamera liefert die Werte und so geht es jetzt:

Code: Alles auswählen

self.cam_width = self.zoom_width = int(self.cam.get(self.PROPID_WIDTH))
self.cam_height = self.zoom_height = int(self.cam.get(self.PROPID_HEIGHT))
Ich habe versucht mit -->YYYY... - Diese Formatierung gibt es so nicht und ich wollte nicht rumprobieren, da es ja für mich keiner Veränderung bedarf. Du wolltest die Reihenfolge aufzeigen und nicht die Formatierung für "datetime" zeigen.

Zeile 118: Die GUI sollte dem Nutzer das aktuelle Verhalten des Modules anzeigen ?!

137(132): Ich wollte ja nie größere Schritte machen und so fand ich es ok --> Da natürich auch größere Schritte möglich sind, sollte es berücksichtigt werden ?!

Das mit "var" sieht jetzt auch schön aus und ist so etwas wie "*kwargs u. *args" bei Parameterübergabe an Funktionen. Das hatte ich nie so richtig verstanden.

tex_on_off: Wiederholungen sollte man nicht machen und zuzammenfassen. Das bastel ich in das Sript rein.

state / get_image: Dies war halt drin, da der Fehler bei OpenCV evt. behoben wird. Beim Abziehen der Kamera wird kein Fehler geworfen,
sondern in der Ausgabe(IDE/Terminal) wird "VIDIOC_DQBUF: No such device" ausgegeben. Das Programm läuft weiter und man hat ein Standbild des letzten Bildes. Man könnte auf Veränderungen des Bildinhaltes mit "np.array_equal" reagieren und einen Fehler auslösen. Das hatte ich schon mal mit Hilfe des Forums gemacht. Hatte ich vergessen :-)

Gruß Frank
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo,

Code: Alles auswählen

    def get_image(self):
        state, frame = self.cam.read()
        
        if np.array_equal(self.last_frame, frame):
            raise RuntimeError("cam disconnected")
            
        self.last_frame = frame
So wird ein "RuntimeError" ausgelöst, wenn die Kamera im Betrieb abgezogen wird.

Gruß Frank
Antworten