raspberrypi / picamera2

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

[HOW-TO] Make a borderless preview window #676

Closed anxuae closed 1 year ago

anxuae commented 1 year ago

I want to have a borderless preview window as it was the case for picamera v1 I've tried to set window flags to the Qt widget, but the window completely disappears.

Here below is my script (to make a Photo Booth application):


#!/usr/bin/python3

import sys
import time
from PyQt5 import QtCore
from picamera2 import Picamera2, Preview

picam2 = Picamera2()
preview_config = picam2.create_preview_configuration()
capture_config = picam2.create_still_configuration()

STARTED = False

for i in range(int(sys.argv[1])):

    # Preview shall be started before call to picam2.start() to avoid error "an event loop is already running"
    picam2.start_preview(Preview.QTGL, x=100, y=100, width=400, height=300)

    # Make a borderless window
    picam2._preview.qpicamera2.setWindowFlags(picam2._preview.qpicamera2.windowFlags(
    ) | QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.FramelessWindowHint)

    # Start the camera (could be good if can be done outside of the sequence loop)
    if STARTED == False:
        picam2.configure(preview_config)
        picam2.start()
        print("Camera started")
        STARTED = True

    time.sleep(5)

    image = picam2.switch_mode_and_capture_image(capture_config, "main")
    print("Capture done")

    # Stop preview after capture, else it will lead to a deadlock
    picam2.stop_preview()
    print("Preview stopped")

    image_name = f"test{i}.jpg"
    image.save(image_name)
    print(f"Image {image_name} saved")

picam2.stop()
davidplowman commented 1 year ago

Hi, thanks for the question. I think there may be a few things to look at here.

Firstly, I don't know if you noticed various warnings about "QApplication was not created in the main() thread". Qt really really really (that's probably still not enough "really"s) doesn't like you running stuff in anything other than the main thread. Getting the Qt preview windows to work (not in the main thread) is quite a struggle, and getting stuff to shut down without dying horribly is infuriatingly difficult. Even the act of importing stuff in a different thread, as you have done, sets it off complaining again.

Now, I suppose we could make it an option to implement this directly in the preview window. In fact, this would be the line to replace by qpicamera2.setWindowFlag(QtCore.Qt.FramelessWindowHint). Though mostly our expectation has been that folks who want custom interfaces would be better off programming their own Qt apps. For example

from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication

from picamera2 import Picamera2
from picamera2.previews.qt import QGlPicamera2

picam2 = Picamera2()
picam2.configure(picam2.create_preview_configuration({"size": (800, 600)}))

app = QApplication([])
qpicamera2 = QGlPicamera2(picam2, width=800, height=600, keep_ar=False)
qpicamera2.setWindowFlag(QtCore.Qt.FramelessWindowHint)
qpicamera2.resize(800, 600)

picam2.start()
qpicamera2.show()
app.exec()

I didn't really understand the comments in your script about deadlocks, as I'm not seeing any behaviour like that. Perhaps it's managing to upset Qt so badly that it stops doing what we expect.

You also have the option of using the DRM preview, that's in many respects a much closer cousin of what the legacy camera system used to do. You get windows without borders (they're not really "windows" at all, they're just overlaid by the display hardware). But of course you can't use it if you want to run X-Windows, because that's how it all works in a Linux system. Don't know if any of that is helpful...

anxuae commented 1 year ago

Hi @davidplowman

Thanks for your quick answer. This almost answering to my question. Correct me if I'm wrong but that means calling picam2.start_preview() with create a QApplication in a thread other than the main one?

I fact, I'm working on a software made with pygame. That why I don't wan't to block the main thread by calling app.exec(). I would like to take benefit of a window optimized with OpenGL on top of pygame window. It was the mechanism that I've implemented with picamera v1, but preview was DRM which is no more possible when X Server is running (ie desktop is running).

Concerning the comment Stop preview after capture, else it will lead to a deadlock, I'm noticed that if I'm calling picam2.stop_preview() before picam2.switch_mode_and_capture_image(capture_config, "main") in my script, the script is deadlocking. Doing a Ctrl+C will stop the process on a lock acquired.

davidplowman commented 1 year ago

Yes, when you're not writing a Qt application, then picam2.start_preview() creates a Qt application in another thread, and everything has to work very hard so that Qt gets no hint that it's not the main thread. In fact, it all gets even more horrendous because you can actually connect multiple cameras and have multiple independent previews which can all start and stop, and the same "main" Qt thread has to keep running throughout. Just horrible.

There was a question previously about rendering with pygame (maybe this one), but I don't know how you would make use of OpenGL to improve it. I do get some plausible hits when I search... but it seems like there's some effort involved.

I'm not averse to adding a "frameless" option to our existing Qt previews. Maybe let me know if you think it's a sufficient solution to finish your project, or whether you find yourself doing something different anyway.

Coming back to the deadlock issue, yes, stopping the preview will cause everything to stall. You can stop a preview (for example to kill any associated window), but you'll need to start another preview (without a window, for example) before trying to capture frames again. "Preview" isn't always the best term, especially when it really means "event loop", but Picamera2 has quite a mixed audience so it can be difficult to know how to present things.

anxuae commented 1 year ago

Hi @davidplowman

Thank you for your feedback, I've now a better understanding of the new picamera API. I will dive into the issue #527 to try a pure pygame preview implementation.

I will re-open the issue if I need help