raspberrypi / picamera2

New libcamera based python library
BSD 2-Clause "Simplified" License
817 stars 173 forks source link

[HOW-TO] #607

Open bnd762 opened 1 year ago

bnd762 commented 1 year ago

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.

Petros626 commented 1 year ago

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).

bnd762 commented 1 year ago

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)
Petros626 commented 1 year ago

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

davidplowman commented 1 year ago

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.

bnd762 commented 1 year ago

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

davidplowman commented 1 year ago

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.

bnd762 commented 1 year ago

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

davidplowman commented 1 year ago

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

bnd762 commented 11 months ago

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)
davidplowman commented 11 months ago

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!