alliedvision / VimbaPython

Old Allied Vision Vimba Python API. The successor to this API is VmbPy
BSD 2-Clause "Simplified" License
93 stars 40 forks source link

Recording with a circular buffer - record previous 10 seconds on trigger #152

Open alapsansky opened 1 year ago

alapsansky commented 1 year ago

I need help setting up a GigE camera to function with a circular recording buffer. I am trying to record a brief event (hummingbird coming to a feeder). It is tough to trigger the camera before this happens.

What I would like to do is stream at 200 fps using a circular buffer and save the previous 10 seconds worth of frames as a video when I hit a key on the keyboard.

I have tried setting

self.cam.AcquisitionMode.set("Recorder") self.cam.RecorderPreEventCount.set(2000)

but I keep getting an error (that the camera is already streaming). I know that I am doing something wrong, but I cannot seem to find an example for how this might be done.

Here is my current code. You will notice that it is based on a response to a previous issue.

import queue
import threading
import time
import pathlib

import cv2
import vimba

class VideoRecorder:
    def __init__(self, cam: vimba.Camera, filename: pathlib.Path, duration: int = 10):
        self.cam = cam
        self.queue = queue.Queue()
        self.filename = filename
        self.record_duration = duration
        # _writer is instantiated in `write_video_file` because there we know the shape of the
        # frames that should be stored in the video file
        self.writer = None

    def __call__(self, cam: vimba.Camera, frame: vimba.Frame):
        # Place the image data as an opencv image and the frame ID into the queue
        if frame.get_status() == vimba.FrameStatus.Complete:
            self.queue.put((frame.as_numpy_ndarray(), frame.get_id()))

        # Hand used frame back to Vimba so it can store the next image in this memory
        cam.queue_frame(frame)

    def record(self):
        with vimba.Vimba.get_instance():
            with self.cam:
                self.cam.ExposureAuto.set('Off')
                self.cam.ExposureTimeAbs.set(4500)
                self.cam.AcquisitionMode.set("Recorder")
                #self.cam.AcquisitionMode.set("Continuous")
                self.cam.RecorderPreEventCount.set(2000)
                try:
                    while True:
                        threading.Thread(target=self.write_video_file, daemon=True).start()
                        self.cam.start_streaming(handler=self)
                        print(f'{self.cam} is recording')
                except KeyboardInterrupt:
                        self.cam.stop_streaming()
                        print(f'{self.cam} is done recording')
                        # Wait until all frames have been taken from the queue
                        self.queue.join()
                        # Close the writer instance that was created to ensure file is finished properly
                        self.writer.release()
                        print(f'{self.writer} is done writing to file')

    def write_video_file(self):
        while True:
            img, id = self.queue.get()
            if id % 100 == 0:
                print(f"took image {id} from queue")
            if self.writer is None:
                height, width = img.shape[0:2]
                fourcc = cv2.VideoWriter_fourcc(*'MJPG')
                # Warning. Shape here is expected as (width, height) np shapes are usually (height,
                # width)
                self.writer = cv2.VideoWriter(self.filename,
                                               fourcc,
                                               10,
                                               (width, height))
            self.writer.write(cv2.cvtColor(img, cv2.COLOR_GRAY2BGR))
            self.queue.task_done()

def main():
    with vimba.Vimba.get_instance() as vmb:
        cams = vmb.get_all_cameras()
        recorders = [VideoRecorder(cam=cam, filename=f'{i}.avi') for i, cam in enumerate(cams)]
        print(f'prepared {len(recorders)} recorders: {list(map(str, recorders))}')
        # Prepare a thread that will run every recorders `record` method. That method blocks until
        # recording is finished
        workers = [threading.Thread(target=recorder.record) for recorder in recorders]
        for worker in workers:
            print(f'starting {worker}')
            worker.start()
        print('All workers running. Waiting for them to finish')
        for worker in workers:
            worker.join()
        print('All workers done')

if __name__ == "__main__":
    main()
Teresa-AlliedVision commented 1 year ago

Hello, I have some further questions: How many cameras do you use and what Posilica models are they? Is it possible, that you are accessing the same camera several times? The code does the settings, before you start streaming, so the error doesn't make a lot of sense to me. Unless the camera is still streaming from a different run of the program.

I see one issue that should have also triggered an error: self.cam.RecorderPreEventCount.set(2000) The camera should not be able to store 2000 frames or are you using a really small ROI? The feature StreamHoldCapacity should tell you how many frames the camera can store.

alapsansky commented 1 year ago

Hi Teresa,

Thank you for your response! I currently have a single GE680 camera connected, but I am hoping that I can write the code such that I can add a second or third camera when the time comes.

I have tried lowering the number of frames from 2000 to 200. The code now runs without error, but no file is saved.

Any ideas?

alapsansky commented 1 year ago

Correction - the same error occurs with a lower number of frames. I simply did not connect to the camera in that first case. However, I noticed that I need to move the lines below to outside of the while True statement.

threading.Thread(target=self.write_video_file, daemon=True).start() self.cam.start_streaming(handler=self)

This fixes the error (that the camera is already streaming). However, I am unable to stop streaming and save the video file. The KeyboardInterrupt inside the record function does not catch. My lack of experience with python is showing. Any help is appreciated.

alapsansky commented 1 year ago

I think I am quite far from achieving my goal with my above code, which is to stream at 200 fps using a circular buffer and save the previous n seconds worth of frames as a video when I hit a key on the keyboard. If anyone can suggest any solution, even for a single camera, it would be greatly appreciated.

Teresa-AlliedVision commented 1 year ago

Apart from the Python code, I think one issue is also the use of the recorder mode in the Prosilica, which can be a bit finnicky and needs to be handled differently than regular streaming. You would need to work with a hardware trigger through GPIO. If the python implementation is an issue, please try the settings of the camera in our Vimba Viewer first. Have a look at this section of the Feature Referece regarding the Recorder settings:

The camera continuously records images into the camera on-board FIFO image buffer, but does not send them to the host until an AcquisitionRecord trigger signal is received. Further AcquisitionRecord trigger events are ignored until acquisition is stopped and restarted. Combined with RecorderPreEventCount, this feature is useful for returning any number of frames before a trigger event. If an AcquisitionRecord trigger is received, the currently imaging image or acquiring image completes as normal, and then at least one more image is taken. The FIFO volatile image memory is a circular buffer, that starts rewriting images once it is full. Its size is determined by AcquisitionFrameCount.

Documentation for Prosilica GE cameras GigE Features Reference PDF