waveform80 / picamera

A pure Python interface to the Raspberry Pi camera module
https://picamera.readthedocs.io/
BSD 3-Clause "New" or "Revised" License
1.57k stars 358 forks source link

Black image in bright sunlight #266

Closed namork closed 7 years ago

namork commented 8 years ago

dear everybody

we recently had our second solar balloon flight to the stratosphere. unfortunately the camera again did't work as planed and tested. we used the following code and got all black photos and videos during the eight hours flight - and sometimes also black files during the filling up/pre flight preparations. is there a possibility that the camera switches to a different mode when exposed to bright sunlight and snow?

here is an overview of the disaster: https://drive.google.com/file/d/0B0iLw9bRs9SGVEV0bVdVQ0dQWVE/view?usp=sharing

any suggestions or similar experience (help highly appreciated)?

greetings from grey zurich

import picamera
import time
from datetime import datetime

count = 0
while count < 999:
    with picamera.PiCamera() as camera:
        camera.resolution = (2592, 1944)
        time.sleep(1)
        camera.start_preview()
        filename = '//DATA/blue_%s_auto.jpg'%datetime.now().strftime('%Y-%m-%d-%H%M%S')
        camera.capture(filename)

        camera.resolution = (2592, 1944)
        camera.shutter_speed = 1000
        camera.exposure_mode = 'off'
        camera.iso = 100
        camera.awb_mode = 'sunlight'
        filename = '//DATA/blue_%s_fixed.jpg'%datetime.now().strftime('%Y-%m-%d-%H%M%S')
        camera.capture(filename)

        camera.resolution = (1920, 1080)
        camera.framerate = 25
        camera.sharpness = 0
        camera.contrast = -10
        camera.saturation = 0
        camera.ISO = 100
        camera.video_stabilization = False
        camera.exposure_compensation = 0
        camera.exposure_mode = 'off'
        time.sleep(1)
        camera.start_preview()
        camera.awb_mode = 'off'
        camera.awb_gains = (1.45, 1.45)
        camera.image_effects = 'none'
        camera.color_effects = None
        camera.rotation = 180
        camera.hflip = False
        camera.vflip = False
        camera.shutter_speed = 1000
        filename = '//DATA/blue_%s.h264'%datetime.now().strftime('%Y-%m-%d-%H%M%S')
        camera.start_recording(filename, bitrate=25000000)
        camera.wait_recording(627)
        camera.stop_recording()
        count = count + 1
waveform80 commented 8 years ago

I've edited your code as I think it's meant to be (you can use ``` to preserve indentation in Markdown). I've tested your script briefly on my development pi (with tweaked paths and a reduced wait on the recording portion obviously) and all the "fixed" photos come out black, and the video is so dark I can only just see a portion of completely white wall the camera is pointed at, so this is an issue with the script rather than the camera.

Anyway, to the black frames: the reason is very likely that you're setting exposure mode to "off" which fixes the camera's gains at their current levels. Given that you've just switched resolution immediately prior to doing so, the gains won't have had time to adjust themselves and may be zero, hence the black frame. Generally the only reason for disabling auto-exposure (and auto-white-balance) is if you want to produce a completely consistent set of shots (e.g. for timelapse production) which isn't the case here, and wouldn't work anyway given that you're re-initializing the whole camera each time round the loop and restarting it each time you set resolution/framerate.

Another reason could be that you're using a shutter-speed of 1ms, which is incredibly short (shutter speed is measured in microseconds). At that rate only the very brightest things are going to show up on your "fixed" photos.

If you're doing something high-risk like balloon flights I would strongly recommend keeping your script as simple as possible (i.e. don't go messing around with AGC, AWB, ISO and everything else - there's just no need) and doing lots of ground testing first in a variety of conditions (average conditions, pointing directly at the sun, in a darkened room at night, etc. etc.). You state you tested your script, but to be blunt I'm having a hard time believing you tested this exact script (given that it straight away produces black frames and overly dark videos on my development pi).

I'd also recommend dropping your video resolution - firstly the 1080p mode doesn't get full FoV (which is probably rather important for something like a balloon flight) and secondly it's going to wind up doing an enormous amount of IO to the card. So, if you want a combination of high-resolution photos and portions of video why not try something much simpler like:

from time import sleep
from datetime import datetime
from picamera import PiCamera

with PiCamera() as camera:
    # The camera's upside down
    camera.rotation = 180

    while True:
        # Set up for high-res photo
        camera.resolution = (2592, 1944)
        camera.framerate = 15
        # Wait to let the auto-exposure figure out good settings
        sleep(2)
        camera.capture('/DATA/%s.jpg' % datetime.now().strftime('%Y-%m-%d-%H%M%S'))
        # Set up for video
        camera.resolution = (1280, 720)
        camera.framerate = 25
        camera.start_recording(
            '/DATA/%s.h264' % datetime.now().strftime('%Y-%m-%d-%H%M%S'),
            quality=22, bitrate=2000000)
        camera.wait_recording(627)
        camera.stop_recording()

I've tweaked the quality and bitrate of the video encoder to reduce the amount of IO that'll be going on (at 720p and quality 22, 2Mbps of bitrate should be sufficient to cover whatever amount of motion is going on during a balloon flight).

namork commented 8 years ago

wow! thank you very much waveform 80!

the reason we chose the pi cam was to get the possibility to have a fixed exposure during the whole flight (in contrast to gopros). we don't like to see exposure changes during a take (if it would auto expose at the beginning, that might be a acceptable).

so you are suggesting, that this is not possible/advisable with the picamera.

we did test the setup in bright sunlight but not in a snowy surrounding. the exposure times should be ok, as the auto exposes pictures even get shorter exposures.

thank you for the tip with 720p (the FoV was right with 1080p, but if you think the data rate is to high for the pi, we will reduce it).

what would be a possible approach to prevent auto exposure and auto white balance, or do we need to better switch to a different camera?

thanks again. your script will be tested this afternoon, greetings from zurich, roman and christina

waveform80 commented 8 years ago

You can get a fixed exposure but in order to do so you mustn't re-initialize the camera (run PiCamera()) or change the resolution or framerate in between shots; changing resolution or framerate resets the camera which in turn resets the gains (the default changed between firmwares - it used to be 0, I think on more modern firmwares it's 1 for both). The firmware only allows you to lock the gains at their current levels (you can't specify the gains manually, in contrast to the AWB gains which can be manually specified) so once you reset the camera it's very difficult to get back to the gains you were at.

Hence, if you really want fixed exposures you'll want something like this:

from time import sleep
from datetime import datetime
from picamera import PiCamera

with PiCamera() as camera:
    # Set resolution and framerate *once*
    camera.resolution = (1280, 720)
    camera.framerate = 25
    # The camera's upside down
    camera.rotation = 180
    # Wait to let the auto-exposure figure out good settings
    sleep(2)
    # Just in case the wait above isn't enough, wait until the gains are
    # sensible
    while camera.analog_gain < 1 or camera.digital_gain < 1:
        sleep(0.1)
    # Lock the gains
    camera.exposure_mode = 'off'
    # Make sure the gains are sensible and bail if they're not
    if camera.analog_gain < 1 or camera.digital_gain < 1:
        raise RuntimeError('low gains')
    # Changed AWB/ISO/etc. here if you want

    while True:
        camera.capture('/DATA/%s.jpg' % datetime.now().strftime('%Y-%m-%d-%H%M%S'))
        camera.start_recording(
            '/DATA/%s.h264' % datetime.now().strftime('%Y-%m-%d-%H%M%S'),
            quality=22, bitrate=2000000)
        camera.wait_recording(627)
        camera.stop_recording()

Bear in mind the above will be capturing images at 1280x720 instead of the full resolution, but you can't change the resolution in the loop and get the same exposure after - changing resolution resets the camera which won't play nicely with disabled auto-exposure. You can set maximum resolution (2592x1944) at the start if you like, but then you can't record video. Basically as soon as you start playing with advanced stuff like disabling auto-exposure, you need to make some trade-offs because you'll be stuck at one resolution+framerate for the duration.

namork commented 8 years ago

again wow! this is incredible helpful. thanks a thousand times!

we slowly start to understand what happened - and that makes us much more confident for future flights.

if we understand correctly: to use different resolutions, we need too let the auto-exposure find good values first. is there a risk doing that (we know that each takes be exposed differently but at least there is no change in the segments themselves)?

``from time import sleep from datetime import datetime from picamera import PiCamera

while True: with PiCamera() as camera:

Set resolution and framerate

camera.resolution = (1280, 720)
camera.framerate = 25
# The camera's upside down
camera.rotation = 180
# Wait to let the auto-exposure figure out good settings
sleep(2)
# Just in case the wait above isn't enough, wait until the gains are
# sensible
while camera.analog_gain < 1 or camera.digital_gain < 1:
    sleep(0.1)
# Lock the gains
camera.exposure_mode = 'off'
# Make sure the gains are sensible and bail if they're not
if camera.analog_gain < 1 or camera.digital_gain < 1:
    raise RuntimeError('low gains')
# Changed AWB/ISO/etc. here if you want
    camera.iso = 100
    camera.shutter_speed = 750
    camera.awb_mode = 'off'
    camera.awb_gains = (1.45, 1.45)
    camera.start_recording(
        '/DATA/%blue_s.h264' % datetime.now().strftime('%Y-%m-%d-%H%M%S'),
        quality=22, bitrate=2000000)
    camera.wait_recording(627)
    camera.stop_recording()
camera.resolution = (2592, 1944)
camera.framerate = 25
# The camera's upside down
camera.rotation = 180
# Wait to let the auto-exposure figure out good settings
sleep(2)
# Just in case the wait above isn't enough, wait until the gains are
# sensible
while camera.analog_gain < 1 or camera.digital_gain < 1:
    sleep(0.1)
# Lock the gains
camera.capture('/DATA/blue_auto_%s.jpg' % datetime.now().strftime('%Y-%m-%d-%H%M%S'))
camera.exposure_mode = 'off'
# Make sure the gains are sensible and bail if they're not
if camera.analog_gain < 1 or camera.digital_gain < 1:
    raise RuntimeError('low gains')
# Changed AWB/ISO/etc. here if you want
    camera.iso = 100
    camera.shutter_speed = 750
    camera.awb_mode = 'off'
    camera.awb_gains = (1.45, 1.45)
    camera.capture('/DATA/blue_fixed_%s.jpg' % datetime.now().strftime('%Y-%m-%d-%H%M%S'))``
waveform80 commented 7 years ago

Sorry, looks like I forgot this one... In case it still matters:

if we understand correctly: to use different resolutions, we need too let the auto-exposure find good values first. is there a risk doing that (we know that each takes be exposed differently but at least there is no change in the segments themselves)?

If you want want to completely consistent exposures you don't get to use multiple resolutions. Changing resolution resets the camera which resets the gains (which won't adjust back to sensible values because the AGC loop has been disabled with exposure_mode = 'off'). So, if you want the same gains every time: pick a resolution and stay in it.

I'll close the ticket for now given it's nearly a year old but do feel free to re-open it if you've got further questions!