raspberrypi / picamera2

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

[HOW-TO] Set exposure measurement area #967

Closed mmoollllee closed 6 months ago

mmoollllee commented 6 months ago

I'm captureing images every couple minutes to produce timelapse videos. My current configuration is not yet achieving the optimal results, as exposure varies much (e.g. if cloudy or not). I'd like to define a specifiy area to measure Exposure. Is that possible, or is there other way to get more even exposure?

My current setup looks like this:

dir = 'some/path'
timestamp = '2024-02-28_15-57'
with Picamera2() as picam2:
      picam2.start(show_preview=False)
      picam2.set_controls({
         "AeMeteringMode": controls.AeMeteringModeEnum.Spot,
         "AeExposureMode": controls.AeExposureModeEnum.Long,
         "Brightness": 0,
         "ExposureValue": 0,
         "FrameDurationLimits": (100, 10000000)
      })
      ctrl={}
      capture_config = picam2.create_still_configuration(ctrl)

      picam2.switch_mode_and_capture_file(capture_config, os.path.join(dir, timestamp+".jpg"))

      picam2.stop()

Thank you for your work!!!

davidplowman commented 6 months ago

Hi, thanks for attaching the helpful test script. Looking at it, I would recommend adding something like time.sleep(0.5) after calling set_controls, as the auto exposure will take a few moments settle.

In your question, you say the the exposure varies too much. To some extent this is unavoidable when changing between cloudy or sunny conditions. Can you say how you would like this to change?

For example, you could restrict the available range of exposure times, but you may simply find that some of your images are over or under exposed. Or you could change the metering mode (for example, from "spot" to "matrix"), but this may or may not make much difference. You can get quite precise control of these behaviours in the camera tuning file, but it would be helpful to know what you would like to achieve. Thanks!

mmoollllee commented 6 months ago

Thank you for you fast answer and the tipp with time.sleep(0.5). I will implement this right away and see if it helps!

To clarify the case see the following two pictures taken in 5 minutes. I thought to solve this behaviour it would help to define a area on the sensor that should be used for exposure calculation (see red rectangle area). Is that possible? From documentation I know there is a "AeMeteringMode": controls.AeMeteringModeEnum.Custom, but I have no idea how to set this up... Sorry for my missing knowledge :(

2024-02-28_12-15-21 2024-02-28_15-20-06

davidplowman commented 6 months ago

Yes, you can customise the metering area. You will need to edit the camera tuning file to do this.

Normally, you should find this in the folder /usr/share/libcamera/ipa/rpi/vc4 for Pi 4 or earlier devices. Look for a file named <your-camera>.json. For example, the camera module 3 would be imx708.json (though note that there is a wide angle version). Anyway, the easiest way to check you've found the right file is to insert a few characters of garbage at the top and check that the camera doesn't start!

Once you have this file, save a backup (just in case!), then open it and search for "metering_modes". There you will find a list of 15 weights for each mode that are applied to different parts of the image. You can set to zero any regions that you don't care about. The map of where the regions lie can be found in section 5.9.2 of the camera tuning guide (page 31).

For Pi 5 the arrangements are similar, but slightly different. Let me know if you are using a Pi 5.

mmoollllee commented 6 months ago

Awesome!! I understand. So for my case I came up with something like [5,4,4,1,4,3,3,1,1,0,2,0,0,1,1].

I'll try out and see what the results look like tomorrow!

From checking examples/tuning_file.py I came up with the following to set custom weights in python before starting the camera.

dir = 'some/path'
timestamp = '2024-02-28_15-57'

# load tuning file
tuning = Picamera2.load_tuning_file("imx477.json")
algo = Picamera2.find_tuning_algo(tuning, "rpi.agc")
algo["metering_modes"]["custom"] = {"weights": [5,4,4,1,4,3,3,1,1,0,2,0,0,1,1]}

with Picamera2(tuning=tuning) as picam2:
      picam2.start(show_preview=False)
      picam2.set_controls({
         "AeMeteringMode": controls.AeMeteringModeEnum.Custom,
         "AeExposureMode": controls.AeExposureModeEnum.Long,
         "Brightness": 0,
         "ExposureValue": 0,
         "FrameDurationLimits": (100, 10000000)
      })

      # auto exposure might take a few moments settle
      time.sleep(0.5)

      ctrl={}
      capture_config = picam2.create_still_configuration(ctrl)

      picam2.switch_mode_and_capture_file(capture_config, os.path.join(dir, timestamp+".jpg"))

      picam2.stop()

From what I understand I can't set the metering areas without restarting the camera, e.g. by clicking on a preview window as it's done on Smartphone devices?

Thanks for your help!!

davidplowman commented 6 months ago

Yes, you will have to completely shut down and restart the camera system to force it to re-read the camera tuning file. libcamera does not expose an API to change this at runtime - though I suppose it might do one day if enough people ask for it!

mmoollllee commented 6 months ago

Yes, you will have to completely shut down and restart the camera system to force it to re-read the camera tuning file.

That's actually already happening in my code, right?

libcamera does not expose an API to change this at runtime - though I suppose it might do one day if enough people ask for it!

Where should I ask for it? In the raspberrypi/libcamera repo?

Still monitoring the change, but so far I think it already works better here

Thank you again!

davidplowman commented 6 months ago

Yes, looks to me like your code should be doing that!. I said there's no API for updating this at runtime, but you can switch between metering modes at runtime, for example with

picam2.set_controls({'AeMeteringMode': controls.AeMeteringModeEnum.Custom})

I suppose you could switch between modes interactively just to test that you're getting difference you expect.

montmejat commented 2 months ago

Yes, you will have to completely shut down and restart the camera system to force it to re-read the camera tuning file. libcamera does not expose an API to change this at runtime - though I suppose it might do one day if enough people ask for it!

Hello, @davidplowman when you say that you have to restart the camera system, can this be done without rebooting the Raspberry Pi? Thanks!

davidplowman commented 2 months ago

You should be able to close the Picamera2 object (picam2.close()) and re-open it, and I think that's enough.

montmejat commented 2 months ago

@davidplowman

In the tuning file, we can see that there are 3 different channels:

            "rpi.agc":
            {
                "channels": [
                    {},
                    {},
                    {}
                ]
            }

What do these 3 different channels correspond to? How can we select which channel to use? Why are there 3 different channels? The first one is selected by default, correct?

I noticed that they are mostly identical with the second one having "base_ev": 0.125 and the third one having "base_ev": 1.5. I'm guessing that it's to have higher or lower overall image brightness.

In the documentation we can choose the following settings:

image

If I want select controls.AeMeteringModeEnum.Custom, all I have to do is:

            "rpi.agc":
            {
                "channels": [
                    {
                        "metering_modes":
                        {
                            "centre-weighted":
                            {
                                "weights":
                                [
                                    3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0
                                ]
                            },
                            "spot":
                            {
                                "weights":
                                [
                                    2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
                                ]
                            },
                            "matrix":
                            {
                                "weights":
                                [
                                    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
                                ]
                            },
                            "custom":
                            {
                                "weights":
                                [
                                    // My custom weights! (top secret)
                                ]
                            }
                        },

Thanks again :smile:

davidplowman commented 2 months ago

Use channel 0, which is selected by default. libcamera has no API actually to choose different channels, but they may be used internally if you were to engage a particular HDR mode. But I don't think this is relevant to you, so as I said, channel 0.

As regards setting custom weights, yes that's correct!