picamera icon indicating copy to clipboard operation
picamera copied to clipboard

Hardcoding timestamps onto image/video frames with the picamera library

Open mfreeborn opened this issue 3 years ago • 0 comments

Asking this here as I've received no responses on the Raspberry Pi forums or the Raspberry Pi Stack Exchange

My goal is for videos and images to have timestamps on them for an application I'm writing which uses the Python picamera library.

My current implementation modifies the annotate_text attribute using an image and video custom encoder. The reason I've opted for a custom encoder approach rather than a custom output is that I need to hook into the capturing apparatus and set the timestamp immediately before the capture is taken. I also want the timestamps to be there by default; from an ease of use point of view, I shouldn't need to think about the timestamps everywhere else in the code, whether I'm using capture(), capture_continuous() or start_recording(), or using other custom outputs.

Here's my implementation for videos, which I think is as good as I can get:

class TimestampedVideoEncoder(picamerax.PiCookedVideoEncoder):
    """Extended video encoder which inserts timestamps on video frames."""

    def stamp(self):
        self.parent.annotate_text = datetime.datetime.now().strftime("%d/%m/%Y %H:%M:%S")

    def start(self, output, motion_output=None):
        # this method is called once just before capturing commences, so here we can set
        # the initial timestamp
        self.stamp()
        super().start(output, motion_output)

    def _callback_write(self, buf):
        # this method is called at least once per frame, so here we can update the timestamp.
        # We will only do so after whole I frames and P frames, just to reduce the number of
        # calls to .stamp() a little.
        # Note that this actually sets the timestamp that will appear on the subsequent frame, which
        # doesn't matter for videos unless we are are recording at a very low framerate. I think this
        # is a limitation that we have to live with - I don't see how we can hook into a point immediately
        # before a new frame starts being captured
        ret = super()._callback_write(buf)
        if (buf.flags & mmal.MMAL_BUFFER_HEADER_FLAG_FRAME_END) and not (
            buf.flags & mmal.MMAL_BUFFER_HEADER_FLAG_CONFIG
        ):
            self.stamp()
        return ret

What I'm struggling a bit more with is images. I'm testing with capture_continuous and I'm getting some behaviour that I can't explain: despite confirming that annotate_text is set to the correct timestamp before each call to super().start(output), the timestamp that visually appears on each image is only updated every other capture. So, when I set the capture_continuous to take a picture every 5 seconds, the timestamps will be:

  • [image 1 -> 09:00:00],
  • [image 2 -> 09:00:00],
  • [image 3 -> 09:00:10],
  • [image 4 -> 09:00:10],
  • [image 5 -> 09:00:20],
  • etc...

Here's the code for the image encoder:

class TimestampedImageEncoder(picamerax.PiCookedOneImageEncoder):
    """Extended image encoder which inserts timestamps on image frames."""

    def stamp(self):
        self.parent.annotate_text = datetime.datetime.now().strftime("%d/%m/%Y %H:%M:%S")

    def start(self, output):
        # this method is called once just before capturing commences, so here we can set
        # the timestamp before the capture process takes place
        self.stamp()
        super().start(output)

Is there an elegant way to achieve what I'm trying to do?

mfreeborn avatar May 02 '21 08:05 mfreeborn