Python: Tkinter GUI

Вопросы написания собственного программного кода (на любых языках)

Модератор: Olej

Аватара пользователя
Olej
Писатель
Сообщения: 16968
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Python: Tkinter GUI

Непрочитанное сообщение Olej » 04 янв 2019, 14:56

Возникла такая интересная задача... :
- сформированные программами распознавания лиц на Python (см. распознавание лиц) выводить в окна-фреймы-виджеты GUI приложений...
- это должно быть на Python 3 (не Python 2).

P.S. Я по этому поводу даже специальное руководство написал для заказчика ... поскольку это не оговаривалось и не запрещалось - приложу его сюда, может кому-то пригодится.
Вложения
Tkinter_GUI.6.4.odt
(156.93 КБ) 77 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 16968
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Python: Tkinter GUI

Непрочитанное сообщение Olej » 04 янв 2019, 15:07

Olej писал(а): - сформированные программами распознавания лиц на Python (см. распознавание лиц) выводить в окна-фреймы-виджеты GUI приложений...
Вопрос в том, как внутренние форматы хранения изображений пакетов а). numpy (он же - компьютерное зрение OpenCV) и б). Pillow (он же Dlib и OpenFace) подсунуть на вывод графической системе Tcl/Tk через Python3 модуль tkinter? ... а заодно и взаимные преобразования форматов между а). и б).

Преобразование форматов (с их отображением) - pil2cv.py:

Код: Выделить всё

#!/usr/bin/python3 
# -*- coding: utf-8 -*- 
import sys
import numpy
from PIL import Image
import cv2
import dlib
import subprocess

# Take the image file name from the command line
with Image.open( sys.argv[ 1 ] ) as pillow_image:
    pillow_image.show()
    cv_image = numpy.array( pillow_image )
    cv_image = cv2.resize( cv_image, ( 0, 0 ), fx = 0.6, fy = 0.6 )
    print( type( cv_image ), cv_image.ndim, cv_image.shape, cv_image.dtype )
    print( '[0][0]=>{} , [0][1]=>{}'.
           format( cv_image[ 0 ][ 0 ], cv_image[ 0 ][ 1 ] ) )
    if cv_image.ndim > 2:  
        pillow_image = Image.fromarray( cv_image, "RGB" )
    else:
        pillow_image = Image.fromarray( cv_image, "L" )
    pillow_image.show()
    print( type( pillow_image ), pillow_image.size, pillow_image.mode )
    print( '(0,0)=>{} , (1,0)=>{}'.
           format( pillow_image.getpixel( ( 0, 0 ) ), pillow_image.getpixel( ( 1, 0 ) ) ) )
dlib.hit_enter_to_continue()
subprocess.call( [ 'killall', 'display' ] )
С этим кодом небольшая загвоздка состоит в том, что сам вывод изображений на экран Pillow (по крайней мере в Debian Linux ... да и в не Debian, похоже, тоже) делает через графические средства проекта ImagMagic, который нужно предварительно установить ... что особенно важно на одноплатных ARM SBC, типа Dragon Board 410c, Rapsbery Pi, Orange Pi ...
Вложения
pil2cv.py
(1.86 КБ) 83 скачивания

Аватара пользователя
Olej
Писатель
Сообщения: 16968
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Python - графика

Непрочитанное сообщение Olej » 04 янв 2019, 15:16

Olej писал(а): Преобразование форматов (с их отображением) - pil2cv.py:
Вывод самых разнообразных внешних файлов изображений в фрейм Tcl/Tk - fil2tk.py:

Код: Выделить всё

#!/usr/bin/python3
# -*- coding: utf-8 -*- 
import sys
import numpy as np
import tkinter as tk 
from tkinter import filedialog

root = tk.Tk()
root.geometry( '600x400' )
#root.geometry('600x400+200+100')
#root.resizable( width = False, height = False )
root.title( 'Изображение' )
b1 = tk.Button( root )
b2 = tk.Button( root, text = 'Открыть' )

def showImage():
    options = {                                          # define options for opening
        'title'     : "Choose an image file",
        'filetypes' : [ ( "Image", ("*.jpg","*.png","*.gif" ) ), 
                        ( "Other", ("*.ppm","*.pgm","*.bmp" ) ), 
                        ( "All files", "*.*" ) ] 
              }
    file_name = filedialog.askopenfilename( **options )  # select image file
    global b1
    b1.destroy()
    print( 'image file: {}'.format( file_name ) )
    print( root.geometry() )
    try:                                                 # load image as is ( .jpg, .png, .gif, ... )
        tkimg = tk.PhotoImage( file = file_name )
    except Exception as err:
        print( 'error: {}'.format( err ) )
        return
    print( type( tkimg ) )
    print( tkimg.__str__() )
    print( 'height={} width={}'.format( tkimg.height(), tkimg.width() ) )
    b1 = tk.Button( root, image = tkimg )
    b1.image = tkimg      # store a reference to the image as an attribute of the widget
    b1.pack( side = 'top' )
    return    

b2[ 'command' ] = showImage                              #b.bind( '<Button-1>', showImage )
b2.pack( side='bottom' )
root.mainloop()
Выглядит это так, как показано на картинках ниже.
Так же точно выглядит выполнение последующих экзамплов.
Вложения
fil2tk.py
(2.29 КБ) 75 скачиваний
f1.png
f1.png (22.35 КБ) 2688 просмотров
p2.png

Аватара пользователя
Olej
Писатель
Сообщения: 16968
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Python - графика

Непрочитанное сообщение Olej » 04 янв 2019, 15:26

Olej писал(а): Вывод самых разнообразных внешних файлов изображений в фрейм Tcl/Tk - fil2tk.py:
Вывод внутренних представлений изображений масссивами numpy (и OpenCV)...
В документации (по tkinter, которая очень скудная) утверждается, что вызов PhotoImage() с ключевым параметром data=… (вместо file=…) создаёт образ изображения для отображения, используя данные уже размещённые в памяти.
Но! Как оказалось, PhotoImage() готов принять, в виде данных, в этом случае не поток сырых байт, а только полный формат представления одного из предусмотренных форматов файлов (с заголовками, «магическими байтами», сжатием для сжимаемых форматов и т. д.).
Для решения этой проблемы (а также для любых пакетов Python и их форматов) нужно сформировать поток байт в памяти, имитирующий простейшие форматы изображений. И здесь нам на помощь приходят так называемые простые форматы изображения PNM (Netpbm format: .pbm, .pgm, .ppm). Мы должны сформировать соответствующий заголовок формата, а далее следующий за ним поток байт собственно изображения (см. Portable anymap).
Приложение cv2tk.py :

Код: Выделить всё

#!/usr/bin/python3 
# -*- coding: utf-8 -*- 
import numpy as np
import cv2
import tkinter as tk 
from tkinter import filedialog

root = tk.Tk()                                           # main frame
root.geometry( '600x400' )
#root.geometry('600x400+200+100')
#root.resizable( width = False, height = False )
root.title( 'Изображение' )
b1 = tk.Button( root )                                   # image frame
b2 = tk.Button( root, text = 'Открыть' )

def showImage():
    options = {                                          # define options for opening
        'title'     : "Choose an image file",
        'filetypes' : [ ( "Image", ("*.jpg","*.png","*.gif" ) ), 
                        ( "Other", ("*.ppm","*.pgm","*.bmp" ) ), 
                        ( "All files", "*.*" ) ]
              }
    file_name = filedialog.askopenfilename( **options )  # select image file
    global b1
    b1.destroy()
    print( 'image file: {}'.format( file_name ) )
#    print( root.geometry() )
    try:                                                 # load image as is ( .jpg, .png, .gif, ... )
        image = cv2.imread( file_name, cv2.IMREAD_LOAD_GDAL )
        if not isinstance( image, np.ndarray ):   # to see if reading error occurs
            raise Exception ( 'Error format image file', type( image ) )
    except Exception as err:
        print( 'error: {}'.format( err ) )
        return
    print( type( image ), image.ndim, image.shape, image.dtype )  
    print( '[0][0]=>{}'.format( image[ 0 ][ 0 ] ) )    
    ( h, w ) = ( image.shape[ 0 ], image.shape[ 1 ] ) 
    if image.ndim > 2:                                   # if RGB scale
        pnm_type = b'P6 '
    else:                                                # if gray scale
        pnm_type = b'P5 '
    head = pnm_type + bytes( str( w ) + ' ' + str( h ) + ' 255 ', 'UTF8' )   
#    print( head ) 
    xdata = head + image.tobytes()
    tkimg = tk.PhotoImage( width = w, height = h, data = xdata, format = 'PPM' )    
    print( 'height={} width={}'.format( tkimg.height(), tkimg.width() ) )
    b1 = tk.Button( root, image = tkimg )
    b1.image = tkimg                                     # store a reference to the image as an attribute of the widget
    b1.pack( side = 'top' )
    return    

b2[ 'command' ] = showImage                              # b.bind( '<Button-1>', showImage )
b2.pack( side='bottom' )
root.mainloop()
Вложения
cv2tk.py
(2.36 КБ) 72 скачивания

Аватара пользователя
Olej
Писатель
Сообщения: 16968
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Python - графика

Непрочитанное сообщение Olej » 04 янв 2019, 15:40

Olej писал(а): Вывод внутренних представлений изображений масссивами numpy (и OpenCV)...
Вывод тех же внутренних представлений изображений, но так как это принято в Pillow (PIL).
Это, должно быть, проще ... но тут выяснилась сложность, с которой я бодался довольно долго:
В большинстве Linux дистрибутивов, которые я пересмотрел:

Код: Выделить всё

$ python3
Python 3.7.2rc1 (default, Dec 12 2018, 06:25:49) 
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import PIL
>>> PIL.__version__
'5.3.0'
>>> from PIL import Image
>>> from PIL import ImageTk
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: cannot import name 'ImageTk'
>>> 

Нет там в пакете Pillow модуля ImageTk, который необходим tkinter !
(я не знаю как это выглядит в каком-то говённом Windows, для которого в первую лчередь разрабатывали изначально Pillow/PIL, но в Linux это именно так)
Проблема здесь не в дистрибутиве или версиях (Python и Pillow), а в том, что пакет Pillow в репозиториях PyPI собран с опциями: --dsable-tcl и --disable-tk (как это описывается здесь) — модуля ImageTk нет и он не может быть импортирован. Для того, чтобы исправить ситуацию нужно переустановить Pillow сборкой из исходников - Pillow 5.4.0:

Код: Выделить всё

$ ls -l Pillow*
-rw-r--r-- 1 olej olej 15929265 янв  3 16:36 Pillow-5.4.0.tar.gz

$ tar -zxf Pillow-5.4.0.tar.gz 

$ du -hs Pillow-5.4.0 
30M	Pillow-5.4.0
Сборка и установка (опции --enable-tcl и --enable-tk установлены по умолчанию если их не запрещать):

Код: Выделить всё

$ cd Pillow-5.4.0

$ time sudo python3 setup.py build_ext install
running build_ext
building 'PIL._imaging' extension
creating build
creating build/temp.linux-x86_64-3.7
creating build/temp.linux-x86_64-3.7/src
creating build/temp.linux-x86_64-3.7/src/libImaging
...
Processing Pillow-5.4.0-py3.7-linux-x86_64.egg
Copying Pillow-5.4.0-py3.7-linux-x86_64.egg to /usr/local/lib/python3.7/dist-packages
Adding Pillow 5.4.0 to easy-install.pth file

real	0m17,559s
user	0m15,633s
sys	0m1,710s
После чего:

Код: Выделить всё

$ python3
Python 3.7.2rc1 (default, Dec 12 2018, 06:25:49) 
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import PIL
>>> PIL.__version__
'5.4.0'
>>> from PIL import Image
>>> from PIL import ImageTk
У нас изменилась версия Pillow, но это не суть важно, важно то, что теперь модуль ImageTk присутствует!

Аватара пользователя
Olej
Писатель
Сообщения: 16968
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Python - графика

Непрочитанное сообщение Olej » 04 янв 2019, 15:45

Olej писал(а):У нас изменилась версия Pillow, но это не суть важно, важно то, что теперь модуль ImageTk присутствует!
Отображение Pillow форматов - pol2tk.py:

Код: Выделить всё

#!/usr/bin/python3 
# -*- coding: utf-8 -*- 
import tkinter as tk 
from tkinter import filedialog
from PIL import Image, ImageTk

root = tk.Tk()                                           # main frame
root.geometry( '600x400' )
#root.geometry('600x400+200+100')
#root.resizable( width = False, height = False )
root.title( 'Изображение' )
b1 = tk.Button( root )                                   # image frame
b2 = tk.Button( root, text = 'Открыть' )

def showImage():
    options = {                                          # define options for opening
        'title'     : "Choose an image file",
        'filetypes' : [ ( "Image", ("*.jpg","*.png","*.gif" ) ), 
                        ( "Other", ("*.ppm","*.pgm","*.bmp" ) ), 
                        ( "All files", "*.*" ) ]
              }
    file_name = filedialog.askopenfilename( **options )  # select image file
    global b1
    b1.destroy()
    print( 'image file: {}'.format( file_name ) )
#    print( root.geometry() )
    try:                                                 # load image as is ( .jpg, .png, .gif, ... )
        image = Image.open( file_name )
    except Exception as err:
        print( 'error: {}'.format( err ) )
        return
    print( '{} ({}, {})'.format( type( image ), 
           image.height, image.width, type( image.getpixel( ( 0, 0 ) ) ) ) )  
    print( '(0,0)=>{}'.format( image.getpixel( ( 0, 0 ) ) ) )    
    tkimg = ImageTk.PhotoImage( image )
    print( 'height={} width={}'.format( tkimg.height(), tkimg.width() ) )
    b1 = tk.Button( root, image = tkimg )
    b1.image = tkimg                                     # store a reference to the image as an attribute of the widget
    b1.pack( side = 'top' )
    return    

b2[ 'command' ] = showImage                              # b.bind( '<Button-1>', showImage )
b2.pack( side='bottom' )
root.mainloop()
Вложения
pil2tk.py
(1.84 КБ) 75 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 16968
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Python: Tkinter GUI

Непрочитанное сообщение Olej » 21 янв 2019, 19:10

Olej писал(а): Возникла такая интересная задача... :
- сформированные программами распознавания лиц на Python (см. распознавание лиц) выводить в окна-фреймы-виджеты GUI приложений...
- это должно быть на Python 3 (не Python 2).

P.S. Я по этому поводу даже специальное руководство написал для заказчика ... поскольку это не оговаривалось и не запрещалось - приложу его сюда, может кому-то пригодится.
Как оказывается, Tkinter очень продуктивный инструмент для быстрой разработки, как и сам Python ... именно для быстрой разработки заметно интереснее, чем альтернативные PyQt или PyGTK.
Единственно, по Python + Tkinter документации (по API) в десятки раз меньше, чем по другим инструментам графики.
Поэтому собираем эффективные (не "для чайников") ресурсы по API Tkinter:

tkinter — Python interface to Tcl/Tk - в выпадающем списке там можете выбрать конкретную версию Python для уточнения.

An Introduction to Tkinter (Work in Progress)

Tkinter 8.5 reference: a GUI for Python

Курс по библиотеке Tkinter языка Python

Если эти страницы открыть в браузере на время написания Python/Tkinter кода, то этого, кажется, вполне достаточно в качестве онлай-справки для написания кода.

Аватара пользователя
Olej
Писатель
Сообщения: 16968
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Python - графика

Непрочитанное сообщение Olej » 23 янв 2019, 17:03

Olej писал(а):Возникла такая интересная задача... :
Продолжение задачи...
- нужно из видеопотока нарезать отдельные изображения лиц, для заполнения каталога эталонных изображений в распознавании лиц...
- видеопотоком может быть, на выбор, альтернативно: а). WEB камера, одна из нескольких на выбор, б). записанный видеофайл .avi/.mkv, в). каталог с линейным набором файлов-картинок .jpg/.png/.gif/.ppm/.pgm/.bmp ...
- нужно простое и быстрое в работе приложение для таких целей.

Код: Выделить всё

#!/usr/bin/python3 
# -*- coding: utf-8 -*- 
import tkinter as tk 
from tkinter import filedialog
from PIL import Image, ImageTk
import threading
import numpy as np
import sys, os, time, argparse 
from common import *

parser = argparse.ArgumentParser()                            # construct the argument parse and parse the arguments
parser.add_argument( '-i', '--image', required = False, help = 'video stream source' )
parser.add_argument( '-k', '--known', required = False,  help = 'known persons directory' )
parser.add_argument( '-v', '--verbose', action = 'count', help = 'increase output verbosity' )

args = vars( parser.parse_args() )
debug = set_debug( args[ 'verbose' ] )                        # verbose level
if debug > 1: print( args )
if debug: print( get_version() )
dir_name = './'                                               # known faces directory
if args[ 'known' ] != None:
    dir_name = args[ 'known' ]
    if not os.path.isdir( dir_name ):
        print( 'illegal directory: '.format( dir_name ) )
        sys.exit( 1 )

root = tk.Tk()                                                # main frame
root.geometry( '640x480+200+100' )
root.resizable( width = False, height = False )
root.title( 'Select images, ' + get_version() )
c1 = tk.Canvas( root ) 

lock = threading.Semaphore()
finish = False                                                # quit flag
image = None

def runImage():
    global lock, finish, image 
    sourcer = get_source( args[ 'image' ] )                   # get images sourcer 
    first = True
    try:                                                      # while exit...
        while not finish:
            lock.acquire()
            ret, frame, delay = sourcer()                     # grab a single frame 
            lock.release()         
            if not ret: break                                 # end of stream 
            if not isinstance( frame, np.ndarray ):           # wrong format file 
                continue   
            if finish: break
            if 2 == frame.ndim:                               # gray scale to BGR
                frame = cv2.cvtColor( frame, cv2.COLOR_GRAY2BGR )
            if first and debug > 0: 
                height = np.size( frame, 0 ); width = np.size( frame, 1 ) 
                print( 'OpenCV: w={} , h={}'.format( width, height ) )
            frame = cv2.cvtColor( frame, cv2.COLOR_BGR2RGB )  # convert from BGR to RGB
            image = Image.fromarray( frame, 'RGB' )           # PIL image       
            if finish: break
            if first and debug > 0:
                height = image.height; width = image.width
                print( 'PIL: w={} , h={}'.format( width, height ) )
            tkimg = ImageTk.PhotoImage( image )               # Tk image
            if finish: break
            if first and debug > 0: 
                print( 'PhotoImage: w={} , h={}'.format( tkimg.width(), tkimg.height() ) )
            c1.winfo_toplevel().geometry( '{}x{}+200+100'.format( tkimg.width(), tkimg.height() + 50 ) )
            if finish: break
            c1.create_image( tkimg.width() / 2, tkimg.height() / 2, image = tkimg )
            c1.pack( side = 'top', fill = 'both', expand = True ) 
            if finish: break
            time.sleep( delay / 1000. )
            first = False
    except ( AttributeError, RuntimeError, KeyboardInterrupt, Exception ) as error: 
        lock.release()
        if debug > 1: print( 'thread function exception' ) 
        return 
    else:
        if debug > 1: print( 'thread function break' ) 
        return 

def on_delete():
    global dir_name
    options = {                                               # define options for deleting
        'title'       : 'Delete image file',
        'filetypes'   : [ ( "Image", ("*.jpg","*.png","*.gif" ) ), 
                          ( "Other", ("*.ppm","*.pgm","*.bmp" ) ), 
                          ( "All files", "*.*" ) ],
              }
    title = filedialog.askopenfilename( **options )
    if 0 == len( title ): return                              # cancel
    n = title.rfind( os.sep ) 
    dir_name = title[ 0:n ]
    os.remove( title )

b2 = tk.Button( root, text = 'Delete image' )
b2.pack( side = 'bottom' )
b2[ 'command' ] = on_delete                                   # b2.bind( '<Button-1>', on_delete )

file_name = '.jpg'
def on_save():
    global file_name, dir_name, lock 
    lock.acquire()
    options = {                                               # define options for saving
        'title'       : 'Save image as file...',
        'filetypes'   : [ ( "Image", ("*.jpg","*.png","*.gif" ) ), 
                          ( "Other", ("*.ppm","*.pgm","*.bmp" ) ), 
                          ( "All files", "*.*" ) ],
        'initialfile' : file_name,
        'initialdir'  : dir_name 
              }
    title = filedialog.asksaveasfilename( **options )         # select image file
    if 0 == len( title ):                                     # cancel
        lock.release()
        return
    n = title.rfind( os.sep )
    dir_name, file_name = title[ 0:n ], title[ n + 1: ]
    if debug > 1: print( '{} => {} | {}'.format( title, dir_name, file_name ) )
    try: 
        image.save( title )
    except ( ValueError, Exception ) as error:
        print( "error: image file can't save: {}!".format( error ) )
    lock.release()

b1 = tk.Button( root, text = '   Save as...   ' )
b1.pack( side = 'bottom' )
b1[ 'command' ] = on_save                                     # b1.bind( '<Button-1>', on_save )

sys.setswitchinterval( 1 )
thread = threading.Thread( target = runImage )
thread.start()                                                # visualisation video ... 

def on_closing():                                             # exit application 
    global finish
    print( ' ... waiting ...' )
    lock.acquire()
    t1 = time.strftime( '%H:%M:%S' )
    finish = True
    root.destroy()
    thread.join( timeout = 3 )    
    t2 = time.strftime( '%H:%M:%S' )
    if debug > 1: 
        print( '{} ... {} - thread is running: {}'.format( t1, t2, thread.is_alive() ) )
    if( thread.is_alive() ):
        raise KeyboardInterrupt( 'thread not finished: press ^C' )
    lock.release()
    sys.exit() 

def on_closing1():
    lock.acquire()
    on_closing()

root.protocol( 'WM_DELETE_WINDOW', on_closing )               # winclose interrupt
root.bind( '<Escape>', lambda e: on_closing() )               # Esc interrupt
try:
    root.mainloop()
except KeyboardInterrupt:                                     # ^C interrupt 
    if debug > 1: print( '\n***************' )
    on_closing()
Сложность и 1/3 кода свяязаны с тем, что нужно корректно остановить, завершить приложение, и сделать это нужно асинхронно - одновременно с отображением видео в окно.

Код: Выделить всё

olej@ACER:~/2019_WORK/own.WORK/AplitSoft/FaceDL/fred$ ./fred.py -h
usage: fred.py [-h] [-i IMAGE] [-k KNOWN] [-v]

optional arguments:
  -h, --help            show this help message and exit
  -i IMAGE, --image IMAGE
                        video stream source
  -k KNOWN, --known KNOWN
                        known persons directory
  -v, --verbose         increase output verbosity
Из потока -i мы набиваем отдельными изображениями-файлами каталог -k.
Вложения
e1.png
e2.png
e3.png
common.py
(5.62 КБ) 73 скачивания
fred.py
(6.48 КБ) 72 скачивания

Аватара пользователя
Olej
Писатель
Сообщения: 16968
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Python: Tkinter GUI

Непрочитанное сообщение Olej » 26 янв 2019, 17:34

Olej писал(а): Продолжение задачи...
Единственно нехорошая особенность этого кода - он написан в императивном, структурном стиле + пришлось чередовать операторы главной ветви программы и описания используемых функций ... об этом я зафиксировал даже в отдельной теме: Python: предварительное определение функций. Потом, позже, работать с модификацией такого кода будет сплошным гемороем.
Выход - переписать это в объектном стиле:

Код: Выделить всё

#!/usr/bin/python3 
# -*- coding: utf-8 -*- 
import tkinter as tk 
from tkinter import filedialog
from PIL import Image, ImageTk
import threading
import numpy as np
import sys, os, time, argparse 
from common import *

parser = argparse.ArgumentParser()                                      # construct the argument parse 
parser.add_argument( '-i', '--image', required = False, help = 'video stream source' )
parser.add_argument( '-k', '--known', required = False,  help = 'known persons directory' )
parser.add_argument( '-v', '--verbose', action = 'count', help = 'increase output verbosity' )
args = vars( parser.parse_args() )
debug = set_debug( args[ 'verbose' ] )                                  # verbose level
if debug: print( '{}, verbose level {}'.format( get_version(), debug ) )
                                                         
class Application( tk.Frame ):
    global debug
    dir_name = './'                                                     # known faces directory
    file_name = '.jpg'                                                  # last image name  
    lock = threading.Semaphore()
    finish = False                                                      # finish thread flag
    image = None; thread = None
      
    def __init__( self, args ):
        if debug > 1: print( args )
        if args[ 'known' ] != None:
            dir_name = args[ 'known' ]
            if not os.path.isdir( dir_name ):
                print( 'illegal directory: '.format( dir_name ) )
                sys.exit( 1 )
        tk.Frame.__init__( self, master = None )
        root = self.winfo_toplevel()                                    # main window
        root.protocol( 'WM_DELETE_WINDOW', self.on_closing )            # winclose interrupt
        root.bind( '<Escape>', lambda e: self.on_closing() )            # Esc interrupt        
        root.geometry( '640x480+200+100' )
        root.resizable( width = False, height = False )
        root.title( 'Select images, ' + get_version() )
        root.config( padx = 10, pady = 10 )
        self.pack()
        sys.setswitchinterval( 1 )
        self.thread = threading.Thread( target = self.runImage )
        self.thread.start()                                             # visualisation video ... 
        self.c1 = tk.Canvas( self )                                     # canvas for image      
        if debug > 2: print( 'Canvas: {}'.format( self.c1.keys() ) ) 
        self.b1 = tk.Button( self, text = 'Delete image' )
        if debug > 2: print( 'Button: {}'.format( self.b1.keys() ) )
        self.b1[ 'command' ] = self.on_delete 
        self.b1.pack( side = 'bottom' )                
        self.b2 = tk.Button( self, text = '   Save as...   ' )
        self.b2[ 'command' ] = self.on_save  
        self.b2.pack( side = 'bottom' )

    def on_delete( self ):
        self.b1.config( relief = tk.SUNKEN )
        options = {                                                     # define options for deleting
            'title'       : 'Delete image file',
            'filetypes'   : [ ( "Image", ("*.jpg","*.png","*.gif" ) ), 
                              ( "Other", ("*.ppm","*.pgm","*.bmp" ) ), 
                              ( "All files", "*.*" ) ],
                  }
        title = filedialog.askopenfilename( **options )
        if len( title ) != 0:                                           
            n = title.rfind( os.sep ) 
            self.dir_name, file_name = title[ 0:n ], title[ n + 1: ]
            os.remove( title )
            if debug > 0:
                print( 'file {} delete from directory {}'.format( file_name, self.dir_name ) )
        self.b1.config( relief = tk.RAISED )

    def on_save( self ):
        self.b2.config( relief = tk.SUNKEN )
        self.lock.acquire()
        options = {                                                     # define options for saving
            'title'       : 'Save image as file...',
            'filetypes'   : [ ( "Image", ("*.jpg","*.png","*.gif" ) ), 
                              ( "Other", ("*.ppm","*.pgm","*.bmp" ) ), 
                              ( "All files", "*.*" ) ],
            'initialfile' : self.file_name,
            'initialdir'  : self.dir_name 
                  }
        title = filedialog.asksaveasfilename( **options )               # select image file
        if len( title ) != 0:                                           # cancel
            n = title.rfind( os.sep )
            self.dir_name, self.file_name = title[ 0:n ], title[ n + 1: ]
            if debug > 1: 
                print( '{} => {} | {}'.format( title, self.dir_name, self.file_name ) )
            try: 
                self.image.save( title )
            except ( ValueError, Exception ) as error:
                print( "error: image file can't save: {}!".format( error ) )
            else:
                if debug > 0:
                    print( 'file {} add to directory {}'.format( self.file_name, self.dir_name ) )
        self.lock.release()
        self.b2.config( relief = tk.RAISED )
    
    def on_closing( self ):                                             # exit application 
        print( ' ... waiting ...' )
        self.lock.acquire()
        t1 = time.strftime( '%H:%M:%S' )
        self.finish = True
        self.winfo_toplevel().destroy()
        self.thread.join( timeout = 3 )    
        t2 = time.strftime( '%H:%M:%S' )
        if debug > 1: 
            print( '{} ... {} - thread is running: {}'.format( t1, t2, self.thread.is_alive() ) )
        if( self.thread.is_alive() ):
            raise KeyboardInterrupt( 'thread not finished: press ^C' )
        self.lock.release()
        self.quit()

    def runImage( self ):
        sourcer = get_source( args[ 'image' ] )                         # get images sourcer 
        first = True
        try:             
            while not self.finish:                                      # while not exit...
                self.lock.acquire()
                ret, frame, delay = sourcer()                           # grab a single frame 
                self.lock.release()         
                if not ret: break                                       # end of stream 
                if not isinstance( frame, np.ndarray ):                 # wrong format file 
                    continue   
                if self.finish: break
                if 2 == frame.ndim:                                     # gray scale to BGR
                    frame = cv2.cvtColor( frame, cv2.COLOR_GRAY2BGR )
                if first and debug > 1: 
                    height = np.size( frame, 0 ); width = np.size( frame, 1 ) 
                    print( 'OpenCV: w={} , h={}'.format( width, height ) )
                frame = cv2.cvtColor( frame, cv2.COLOR_BGR2RGB )        # convert from BGR to RGB
                self.image = Image.fromarray( frame, 'RGB' )            # PIL image       
                if self.finish: break
                if first and debug > 1:
                    height = self.image.height; width = self.image.width
                    print( 'PIL: w={} , h={}'.format( width, height ) )
                tkimg = ImageTk.PhotoImage( self.image )                # Tk image
                if self.finish: break
                if first and debug > 1: 
                    print( 'PhotoImage: w={} , h={}'.format( tkimg.width(), tkimg.height() ) )
                self.c1.winfo_toplevel().geometry( '{}x{}+200+100'.format( tkimg.width(), tkimg.height() + 70 ) )
                self.c1.configure( width = tkimg.width(), height = tkimg.height() )
                if self.finish: break
                self.c1.create_image( tkimg.width() / 2, tkimg.height() / 2, image = tkimg )
                self.c1.pack( side = 'top', fill = 'both', expand = True ) 
                if self.finish: break
                time.sleep( delay / 1000. )
                first = False
        except ( AttributeError, RuntimeError, KeyboardInterrupt, Exception ): # as error: 
            self.lock.release()
            if debug > 1: print( 'thread function exception' ) 
            return 
        else:
            if debug > 1: print( 'thread function break' ) 
            return 
                                                        
app = Application( args )
try:
    app.mainloop()
except KeyboardInterrupt:                                               # ^C interrupt 
    if debug > 1: print( '\n*************** ^C' )
    app.on_closing()
Выполнение и картинки - всё осталось точно тем же. Специально сделал полный аналог.
Вложения
fredo.py
(8.36 КБ) 70 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 16968
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Python - графика

Непрочитанное сообщение Olej » 02 фев 2019, 15:29

Olej писал(а): Скачивается здесь (Linux): http://www.bitflipper.ca/rapyd/rapyd-1-0-2.tgz

Код: Выделить всё

olej@ACER:~/Загрузки$ ls -l rapyd-1-0-2.tgz 
-rw-r--r-- 1 olej olej 624203 фев  2 14:10 rapyd-1-0-2.tgz
Как видно, это такое минимальное творение... Разархивируем куда-нибудь (в $HOME):

Код: Выделить всё

olej@ACER:~$ ls -l rapyd-1-0-2 
итого 2448
-rw-r--r-- 1 olej olej  32603 июл  1  2014 dnd_realworld.py
-rw-r--r-- 1 olej olej  25751 фев  2 14:16 dnd_realworld.pyc
drwxr-xr-x 2 olej olej   4096 фев  2 14:13 Icons
-rw-r--r-- 1 olej olej  63263 июл  1  2014 rapyd.config
-rw-rw-r-- 1 olej olej  21048 июл  1  2014 RapydDemo.rpj
-rw-rw-r-- 1 olej olej 587514 июл  1  2014 rapyd.help
-rwxr-xr-x 1 olej olej 701951 июл  1  2014 rapyd.py
-rw-r--r-- 1 olej olej   2166 июл  1  2014 rapyd.template
-rw-r--r-- 1 olej olej   4554 июл  1  2014 README.TXT
-rw-r--r-- 1 olej olej  26685 июл  1  2014 rpErrorHandler.py
-rw-r--r-- 1 olej olej  18501 фев  2 14:16 rpErrorHandler.pyc
-rw-r--r-- 1 olej olej 196658 июл  1  2014 rpHelp.py
-rw-r--r-- 1 olej olej 141548 фев  2 14:16 rpHelp.pyc
-rw-rw-r-- 1 olej olej 143436 июл  1  2014 rpOption.py
-rw-r--r-- 1 olej olej 115862 фев  2 14:16 rpOption.pyc
-rw-r--r-- 1 olej olej 215769 июл  1  2014 rpWidgets.py
-rw-r--r-- 1 olej olej 151311 фев  2 14:16 rpWidgets.pyc
-rw-r--r-- 1 olej olej  13402 июл  1  2014 versions.txt
-rw-r--r-- 1 olej olej      0 фев  2 14:20 xlocker.dec
Видно, что это действительно минимально + само написано на Python, ©2014-2015.
Запуск непосредственно из разархивированного каталога:

Код: Выделить всё

olej@ACER:~/rapyd-1-0-2$ ./rapyd.py
...
Вложения
r0.png
(22.67 КБ) 1649 скачиваний
r1.png
r1.png (30.2 КБ) 2574 просмотра
r2.png
r2.png (12.88 КБ) 2574 просмотра
r3.png

Ответить

Вернуться в «Программирование»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 3 гостя