picamera2 icon indicating copy to clipboard operation
picamera2 copied to clipboard

[HOW-TO]

Open bnd762 opened this issue 2 years ago • 10 comments

Is there a way to display the preview window with Tkinter? In PiCamera I was able to place a preview window independently of TKinter. I don't want to use QT in my project.

bnd762 avatar Mar 08 '23 06:03 bnd762

I would say try with a short code example and if it's solid enough you can share it in examples folder via pull request. I'm working too on an option to take pictures by pressing a key and stopping the script with a key (without OpenCV).

Petros626 avatar Mar 08 '23 07:03 Petros626

Here is the process "camera" from my project showing a preview window and capturing. I would like to create the same with Picamera2, but without CV2 and QT, only with Tkinter.

#importing required modules import logging import logging.handlers import multiprocessing import picamera from pathlib import Path import sys

#importing required custom modules from Model.constants import *

#camera child process (overriding "def run" method) def camera(control, control_queues):

#logger
queue_handler = logging.handlers.QueueHandler(control_queues[LOGGING])

#root logger for this child process
root_camera_logger = logging.getLogger()
root_camera_logger.setLevel(logging.DEBUG)
root_camera_logger.addHandler(queue_handler)

#logger for this child process
camera_logger = logging.getLogger("camera logger")
camera_logger.log(logging.INFO, "process started: %s" % multiprocessing.current_process().name)

#multiprocessing logger
camera_mp_logger = multiprocessing.get_logger()
camera_mp_logger.setLevel(logging.DEBUG)
camera_mp_logger.addHandler(queue_handler)

try:
    
    #init picamera
    cam = picamera.PiCamera()

    #place the preview window independent of the tkinter GUI 
    cam.preview_fullscreen = False
    cam.preview_window = (3, 17, 295, 295)
    cam.resolution = (295, 295)

    #start endless loop for the camera process
    while not control[QUIT_PROCESS]:

        if control[CAMERA]:

            if control[TOGGLE_CAMERA]:
                cam.start_preview()
                control[TOGGLE_CAMERA] = False

            if control[CAPTURE]:
                try:
                    cam.capture(str(Path(sys.path[0] + "/Media/Datamatrix/TEST.PNG"))) #DTMX.PNG
                    control[CAPTURE] = False

                except IOError as err:
                    camera_logger.log(logging.DEBUG, "cannot write captured datamatrix code! {0}".format(err))
                    control[CAPTURE] = False

        else:
            cam.stop_preview()

    camera_logger.log(logging.INFO, "camera process closed")

except Exception as e:
    template = "An exception of type {0} in process 'camera' occurred. Arguments:\n{1!r}"
    message = template.format(type(e).__name__, e.args)
    print(message)

bnd762 avatar Mar 08 '23 08:03 bnd762

@bnd762 I would try to replace everything where you used PyQt and/or OpenCV and find the equivalent Tkinter functions.

Petros626 avatar Mar 08 '23 09:03 Petros626

In legacy OS images it was possible to render camera images to the screen directly using the HDMI hardware, but the desktop "knew" nothing about the images that were simply written on top of it. In more recent versions of the OS you need to go through some existing Linux display framework and the old mechanism is no longer supported.

I'm afraid I don't know anything about TkInter, though I expect it must be possible to render camera images that you capture with it (GPU acceleration may be trickier). Sorry not to be more help.

davidplowman avatar Mar 08 '23 10:03 davidplowman

I could pick up the individual images via Mapped Array and display them in Tkinter to simulate a preview. But that's slow

bnd762 avatar Mar 08 '23 13:03 bnd762

In the Qt world you can use software rather than hardware-assisted rendering, and on a Pi 4, so long as you feed it an appropriate image format, and the resolutions aren't too big, it's not terrible. You could try it and see - presumably that's the performance level you could achieve without delving into OpenGL. The camera images are in uncached memory currently, so the best policy is often just to take an immediate copy and work with that.

davidplowman avatar Mar 08 '23 13:03 davidplowman

There must be a way to do this in Python/Tkinter without QT and CV2. It can't be that it doesn't work.

bnd762 avatar Mar 08 '23 14:03 bnd762

Hi, is there anything else to investigate here? Unfortunately we don't have any plans to implement a Tkinter preview at this time.

davidplowman avatar Apr 04 '23 14:04 davidplowman

I have now solved the problem with pygame and it works wonderfully. I use multiprocessing. Pygame runs in a separate process and is only used for the camera. The GUI (tkinter) runs in the main process. In the code example I have solved the problem that the Pygame window is in the foreground on the Raspberry X11 and under Windows.

#importing required modules import logging import logging.handlers import multiprocessing from picamera2 import Picamera2 from pathlib import Path import sys import os

#hide support prompt message os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide" os.environ['LIBCAMERA_LOG_LEVEL'] = "3"

import pygame

#windows only if sys.platform.startswith("win"):

from ctypes import windll

#importing required custom modules from Model.constants import *

#camera child process (overriding "def run" method) def camera(control, events, control_queues):

#helpers

#logger
queue_handler = logging.handlers.QueueHandler(control_queues[LOGGING])

#root logger for this child process
root_camera_logger = logging.getLogger()
root_camera_logger.setLevel(logging.DEBUG)
root_camera_logger.addHandler(queue_handler)

#logger for this child process
camera2_logger = logging.getLogger("picamera2")
camera2_logger.setLevel(logging.INFO)

camera_logger = logging.getLogger("camera logger")
camera_logger.log(logging.INFO, "process started: %s" % multiprocessing.current_process().name)

#multiprocessing logger
camera_mp_logger = multiprocessing.get_logger()
camera_mp_logger.setLevel(logging.DEBUG)
camera_mp_logger.addHandler(queue_handler)

try:
    
    #init picamera##https://github.com/raspberrypi/picamera2/issues/527
    cam = Picamera2()

    #place the preview window independent of the tkinter GUI
    os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (30,17)

    #X11 raspberry only -> set window always on top -> no function!?
    os.environ['SDL_WINDOW_ALWAYS_ON_TOP'] = 'SDL_TRUE'

    #windows only
    if sys.platform.startswith("win"):
        
        SetWindowPos = windll.user32.SetWindowPos
    
    res = (240, 320) #295, 295
    
    cam.preview_configuration.main.size = res
    cam.preview_configuration.main.format = 'BGR888'
    cam.configure("preview")

    #configure still capture
    capture_config = cam.create_still_configuration()
    cam.still_configuration.main.size = (2500,2500)

    #start endless loop for the camera process
    while not events[QUIT_PROCESS].is_set():

        if control[CAMERA]:

            if control[TOGGLE_CAMERA]:

                pygame.init()

                cam.start()

                #borderless window
                screen = pygame.display.set_mode(res, pygame.NOFRAME)
                
                #windows only -> set window always on top
                if sys.platform.startswith("win"):
                    
                    SetWindowPos(pygame.display.get_wm_info()['window'], -1, 3, 17, 0, 0, 0x0001)

                #raspberry X11 only -> set window always on top
                #sudo apt-get install wmctrl
                else:

                    os.system("wmctrl -r 'pygame window' -b add,above")

                control[TOGGLE_CAMERA] = False

            if control[CAPTURE]:######control_events[CAPTURE].wait()
                
                try:
                    
                    #cam.capture(str(Path(sys.path[0] + "/Media/Datamatrix/TEST.PNG"))) #DTMX.PNG
                    cam.switch_mode_and_capture_file(capture_config, str(Path(sys.path[0] + "/Media/Datamatrix/TEST.PNG"))) #DTMX.PNG
                    control[CAPTURE] = False######control_events[CAPTURE].clear()

                except IOError as err:
                    
                    camera_logger.log(logging.DEBUG, "cannot write captured datamatrix code! {0}".format(err))
                    control[CAPTURE] = False######control_events[CAPTURE].clear()

            array = cam.capture_array()
            img = pygame.image.frombuffer(array.data, res, 'RGB')
            screen.blit(img, (0, 0))
            pygame.display.update()

        else:

            #to prevent error check if cam is running
            if cam.started:
                
                cam.stop()
                
                pygame.quit()

    camera_logger.log(logging.INFO, "camera process closed")

except Exception as e:
    
    template = "An exception of type {0} in process 'camera' occurred. Arguments:\n{1!r}"
    message = template.format(type(e).__name__, e.args)
    print(message)

bnd762 avatar Sep 22 '23 07:09 bnd762

Cool, glad it's working. It might be nice just to put a display-a-camera-image-using-pygame example into the examples folder too!

davidplowman avatar Sep 22 '23 07:09 davidplowman