techyian / MMALSharp

C# wrapper to Broadcom's MMAL with an API to the Raspberry Pi camera.
MIT License
195 stars 33 forks source link

Camera warmup time question #201

Closed Kas-code closed 3 years ago

Kas-code commented 3 years ago

For my project, I need to capture images in a tight loop as quickly as possible. After streamlining the rest of the process, the camera warmup time of 2000ms becomes my main bottleneck. If I reduce this time then the image quality suffers, it looks overexposed. I'm ok with waiting 2000ms on the first call, but can I remove the camera warmup time on subsequent calls to capture an image? Can I just do camera setup related stuff on the first call and ignore it on subsequent calls to make it quicker? Is there any other way I can reduce the camera warmup time?

My code is the following, I want to call CaptureRawData() many times as quickly as possible.


public static async Task<byte[]> CaptureRawData()
        {
            MMALCamera cam = MMALCamera.Instance;
            MMALCameraConfig.StillEncoding = MMALEncoding.BGR24;
            MMALCameraConfig.StillSubFormat = MMALEncoding.BGR24;
            using (var imgCaptureHandler = new MemoryStreamCaptureHandler())
            using (var renderer = new MMALNullSinkComponent())
            {
                cam.ConfigureCameraSettings(imgCaptureHandler);
                cam.Camera.PreviewPort.ConnectTo(renderer);

                // Camera warm up time
                await Task.Delay(2000);
                await cam.ProcessAsync(cam.Camera.StillPort));
                return imgCaptureHandler.CurrentStream.ToArray();
            }
        }
techyian commented 3 years ago

Yes, you only need to wait for the camera to warm up after a call to ConfigureCameraSettings and you only need to call that once unless you are changing camera settings in MMALCameraConfig. Let me know if that's not the case, but it is my understanding that this should be ok.

Kas-code commented 3 years ago

Thanks Ian, My first try was the following:

private static bool _camAlreadySetup;

public static async Task<byte[]> CaptureRawData()
{
    MMALCamera cam = MMALCamera.Instance;
    MMALCameraConfig.StillEncoding = MMALEncoding.BGR24;
    MMALCameraConfig.StillSubFormat = MMALEncoding.BGR24;
    using (var imgCaptureHandler = new MemoryStreamCaptureHandler())
    using (var renderer = new MMALNullSinkComponent())
    {
        if (!_camAlreadySetup)
        {
            cam.ConfigureCameraSettings(imgCaptureHandler);
        }
        cam.Camera.PreviewPort.ConnectTo(renderer);
        if (!_camAlreadySetup)
        {
            // Camera warm up time
            await Task.Delay(2000);
        }
        _camAlreadySetup = true;
        await cam.ProcessAsync(cam.Camera.StillPort));
        return imgCaptureHandler.CurrentStream.ToArray();
    }
}

But this throws the following exception on the second call:

Unhandled exception. System.IO.IOException: Stream not writable.
at MMALSharp.Handlers.StreamCaptureHandler`1.Process(ImageContext context)
at MMALSharp.Callbacks.PortCallbackHandler`2.Callback(IBuffer buffer)
at MMALSharp.Ports.Outputs.OutputPort.NativeOutputPortCallback(MMAL_PORT_T* port, MMAL_BUFFER_HEADER_T* buffer)
at MMALSharp.Ports.Outputs.StillPort.NativeOutputPortCallback(MMAL

My second attempt was the following:

private static bool _camAlreadySetup;
private static MemoryStreamCaptureHandler _imgCaptureHandler = new MemoryStreamCaptureHandler();
private static MMALNullSinkComponent _renderer = new MMALNullSinkComponent();

public static async Task<byte[]> CaptureRawData()
{
    MMALCamera cam = MMALCamera.Instance;
    MMALCameraConfig.StillEncoding = MMALEncoding.BGR24;
    MMALCameraConfig.StillSubFormat = MMALEncoding.BGR24;
    if (!_camAlreadySetup)
    {
        cam.ConfigureCameraSettings(_imgCaptureHandler);
        cam.Camera.PreviewPort.ConnectTo(_renderer);
        // Camera warm up time
        await Task.Delay(2000);
    }
    _camAlreadySetup = true;
    await cam.ProcessAsync(cam.Camera.StillPort));
    return _imgCaptureHandler.CurrentStream.ToArray();
}

This variation doesn't throw an exception, but the _imgCaptureHandler.CurrentStream.ToArray() returns an array that's double the size of bytes on the second call, then three times the size, etc... I'm guessing because it hasn't been reset between calls.

You say that ConfigureCameraSettings only needs to be called that once unless you are changing camera settings, but don't I need to call it passing imgCaptureHandler as the parameter in order to setup the imgCaptureHandler?

Kas-code commented 3 years ago

Hi @techyian I know you are very busy at the moment from reading your comments on other posts, but I would really appreciate it if you could find the time to be able to give me a few pointers on this, it's really a critical part of a project I'm working on. If you could help at all it would really mean a lot for my project.

My project is using a new and quite significantly different time lapse technique that no one else in the world has tried before (AFAICT) which has been very successful so far and I can tell you more about it if you're interested, but if there was a way around the 2000ms delay between photos then things would be so much better.

I've tried a lot of things but haven't been able to remove the 2000ms delay so far. It's also a requirement that the photos are captured to memory and not to disk.

If you could help at all that would be great. Thanks.

techyian commented 3 years ago

Hi, apologies for the delay - yes still very busy with other projects at the moment to commit any time to this one unfortunately. My previous answer still stands, you do not need the camera warm up time so long as you're not having to call ConfigureCameraSettings multiple times. Your issue seems to be more focused on the capture handler - can you not have a public variable/property which holds the reference to the capture handler and clear it when you've finished processing it? The underlying stream property is public and should be accessible by your application.

Kas-code commented 3 years ago

@techyian Amazing! Thanks very much for your help. Your comment helped me to eventually figure it out. My problem was that, because each new photo was just getting added on to the end of the stream I needed to be able to clear the stream before every photo without actually closing it. The solution was the following: (making use of the MemoryStream.Clear extension method)


private static bool _camAlreadySetup;
private static MemoryStreamCaptureHandler _imgCaptureHandler = new MemoryStreamCaptureHandler();
private static MMALNullSinkComponent _renderer = new MMALNullSinkComponent();

public static async Task<byte[]> CaptureRawData()
{
        _imgCaptureHandler.CurrentStream.Clear();
    MMALCamera cam = MMALCamera.Instance;
    MMALCameraConfig.StillEncoding = MMALEncoding.BGR24;
    MMALCameraConfig.StillSubFormat = MMALEncoding.BGR24;
    if (!_camAlreadySetup)
    {
        cam.ConfigureCameraSettings(_imgCaptureHandler);
        cam.Camera.PreviewPort.ConnectTo(_renderer);
        // Camera warm up time
        await Task.Delay(2000);
    }
    _camAlreadySetup = true;
    await cam.ProcessAsync(cam.Camera.StillPort));
    return _imgCaptureHandler.CurrentStream.ToArray();
}

public static void Clear(this MemoryStream source)
{
        byte[] buffer = source.GetBuffer();
        Array.Clear(buffer, 0, buffer.Length);
        source.Position = 0;
        source.SetLength(0);
}