Mikroskop mit Tkinter-GUi

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

Dienstag 1. November 2016, 13:58

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

Sonntag 6. November 2016, 19:25

@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: 544
Registriert: Dienstag 13. Februar 2007, 21:57

Mittwoch 9. November 2016, 03:02

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

Montag 21. November 2016, 13:47

@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: 544
Registriert: Dienstag 13. Februar 2007, 21:57

Donnerstag 24. November 2016, 01:23

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: 544
Registriert: Dienstag 13. Februar 2007, 21:57

Freitag 28. Juli 2017, 11:18

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: 544
Registriert: Dienstag 13. Februar 2007, 21:57

Freitag 9. Februar 2018, 11:43

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: 3111
Registriert: Mittwoch 14. Oktober 2015, 14:29

Freitag 9. Februar 2018, 12:12

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: 544
Registriert: Dienstag 13. Februar 2007, 21:57

Sonntag 11. Februar 2018, 05:16

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: 544
Registriert: Dienstag 13. Februar 2007, 21:57

Sonntag 25. März 2018, 01:21

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: 544
Registriert: Dienstag 13. Februar 2007, 21:57

Mittwoch 28. März 2018, 14:34

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: 544
Registriert: Dienstag 13. Februar 2007, 21:57

Donnerstag 29. März 2018, 00:08

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: 544
Registriert: Dienstag 13. Februar 2007, 21:57

Freitag 30. März 2018, 05:04

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: 8112
Registriert: Sonntag 21. Oktober 2012, 17:20

Freitag 30. März 2018, 09:02

@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: 544
Registriert: Dienstag 13. Februar 2007, 21:57

Freitag 30. März 2018, 10:20

Hallo Sirius3,

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

Gruß Frank
Antworten