Skip to content

Camera eposure time doubled from shutter speed? #726

@minhlead

Description

@minhlead

Some how my pi HQ takes 2x the shutter speed time to complete an exposure. I.e: if the shutter speed is 1 minutes, the exposure will take exactly 2 minutes to complete. I can see the camera writes a 0 byte file when the exposure start and fill it up with data after the 2x time passed.
I tried to enable still_stats, disable noise_reduction, turn off awb_mode, exposure_mode, turn on burst and nothing worked.
Am I doing something wrong? Here is the code
`#!/usr/bin/python3

#-----------------------------------------------------------------------

Time lapse service for shooting continuously single images with

automatic exposure adaption day and night.

Copyright (C) 2021 Wolfgang Reissenberger sterne-jaeger@openfuture.de

This application is free software; you can redistribute it and/or

modify it under the terms of the GNU General Public

License as published by the Free Software Foundation; either

version 2 of the License, or (at your option) any later version.

#-----------------------------------------------------------------------

import io, os, stat, sys, math
from time import time, sleep, perf_counter
from datetime import datetime
from configparser import ConfigParser
from pathlib import Path
from picamera import PiCamera
from picamera import Color
from fractions import Fraction
from autoexposure import *

class TimelapseService:
config = None
inifile_name = None
stopfile = None
oldname = None
rates = []
last_shot = 0

def __init__(self):
    # setup configuration
    self.config = ConfigParser(interpolation=None)
    self.config.optionxform = str
    # default values
    self.config.add_section('Camera')
    self.config.set('Camera', 'SensorMode', '3')
    self.config.set('Camera', 'ExposureTime', '400000') # 1/250 sec
    self.config.set('Camera', 'resX', '3040')
    self.config.set('Camera', 'resY', '3040')
    self.config.set('Camera', 'BaseDirectory', "/usr/share/weatherradio/html/media/snapshots")
    self.config.set('Camera', 'ConverterFIFO', "/tmp/imageconverter.fifo")
    self.config.set('Camera', 'ISOSpeedRatings', '50')
    self.config.set('Camera', 'Contrast', '0')
    self.config.set('Camera', 'Brightness', '50')
    self.config.set('Camera', 'Saturation', '0')
    self.config.set('Camera', 'interval', '60')
    self.config.set('Camera', 'zoomX0', '0.0')
    self.config.set('Camera', 'zoomY0', '0.0')
    self.config.set('Camera', 'zoomX1', '1.0')
    self.config.set('Camera', 'zoomY1', '1.0')
    self.config.set('Camera', 'ExposureTimeout', '450')

    # night default settings
    self.config.add_section('Night')
    self.config.set('Night', 'Contrast', '100')
    self.config.set('Night', 'Brightness', '78')
    self.config.set('Night', 'Saturation', '0')
    self.config.set('Night', 'MaxExposure', '50000000') # 20 sec
    self.config.set('Night', 'MaxISO', '300')

    # read ini file
    self.inifile_name = os.path.dirname(os.path.realpath(__file__)) + '/timelapse.ini'
    self.config.read(self.inifile_name)

    self.stopfile = Path('/tmp/timelapse.stop')


def config_camera(self, camera):
    # change to auto exposure for short exposure times
    if self.config.getint('Camera', 'ExposureTime') < 20:
        camera.shutter_speed = 0 # auto
    else:
        camera.shutter_speed = self.config.getint('Camera', 'ExposureTime')

    camera.iso           = self.config.getint('Camera', 'ISOSpeedRatings')
    camera.contrast      = self.config.getint('Camera', 'Contrast')
    camera.brightness    = self.config.getint('Camera', 'Brightness')
    camera.saturation    = self.config.getint('Camera', 'Saturation')
    camera.zoom          = (self.config.getfloat('Camera', 'zoomX0'),
                            self.config.getfloat('Camera', 'zoomY0'),
                            self.config.getfloat('Camera', 'zoomX1'),
                            self.config.getfloat('Camera', 'zoomY1'))

def get_capture_dir(self):
    dir = self.config.get('Camera', 'BaseDirectory')
    # ensure that the image directory exists
    if not Path(dir).exists():
        Path(dir).mkdir(parents=True)
    return dir

def get_target_dir(self, now):
    targetdir = self.config.get('Camera', 'BaseDirectory') + '/' + now.strftime("%Y-%m-%d")
    # ensure that the image directory exists
    if not Path(targetdir).exists():
        Path(targetdir).mkdir(parents=True)
    return targetdir

def get_image_name(self, now, dir=None):
    filename = now.strftime("%Y-%m-%d_%H%M%S") + ".jpg"
    if dir != None:
        filename = dir + '/' + filename
    return filename


def convert_image(self, now, fullname):
    if not self.stopfile.is_file():
        fifo = self.config.get('Camera', 'ConverterFIFO')
        if os.path.exists(fifo) and stat.S_ISFIFO(os.stat(fifo).st_mode):
            with open(fifo, 'w') as pipeout:
                pipeout.write("%s\n" % self.get_image_name(now))
        else:
            # otherwise move image to target directory
            target = self.get_image_name(now, dir=self.get_target_dir(now))
            # mv to target directory
            os.rename(fullname, target)
        # remember the current name
        self.oldname = fullname


def wait_for_converter(self, fullname):
    # wait only if there exists a pipe to the converter
    fifo = self.config.get('Camera', 'ConverterFIFO')
    if os.path.exists(fifo) and stat.S_ISFIFO(os.stat(fifo).st_mode):
        # wait until old image has disappeared
        while self.oldname is not None and Path(self.oldname).is_file() and not self.stopfile.is_file():
            print("%s waiting for image converter ..." % fullname)
            sleep(1)

def single_shot(self, interval, camera):
    # set base parameters that cannot be changed
    self.config_camera(camera)
    # calculate wait time in seconds
    ts_now = time()
    # ensure that diff > interval and or on a 5 secs edge
    diff = ts_now - self.last_shot
    if diff > interval:
        # next 5 secs interval
        diff = (int(ts_now / 5)+1)*5-ts_now
    else:
        # fill up until interval
        diff = interval - diff
    sleep(diff)
    # current capture time
    ts_now = time()
    # start capturing
    start_capture = perf_counter()
    print("Start capturing...")
    now = datetime.fromtimestamp(ts_now)
    fullname = self.get_image_name(now, dir=self.get_capture_dir())
    camera.awb_mode = 'off'
    camera.awb_gains = (1.4, 1.7)
    camera.exposure_mode = 'off'
    fullname = self.get_image_name(now, dir=self.get_capture_dir())
    camera.annotate_foreground = Color('white')
    camera.annotate_background = Color('black')
    camera.annotate_text = "%s | ex=%.3f ms | iso=%d " % (now.strftime("%Y-%m-%d %H:%M:%S"), camera.shutter_speed/1000, camera.iso)
    camera.annotate_text_size = 72
    camera.still_stats = True
    camera.image_denoise = False
    camera.capture_sequence([fullname],burst=True)
    self.last_shot = ts_now

    # wait for the conversion of the previous image to be completed
    self.wait_for_converter(fullname)
    # calculate the optimal exposure time
    (imgExpTime, imgBrightness) = calibrateExpTime(fullname, self.config)

    # store new configuration
    configfile = open(self.inifile_name, 'w')
    self.config.write(configfile)
    configfile.close()

    print("date=%s time=%s file=%s ex=%d iso=%d br=%s sat=%d co=%d img_brightness=%d sleep=%0.1f speed=%d" % (now.strftime("%Y-%m-%d"), now.strftime("%H:%M:%S"), fullname, imgExpTime, camera.iso, camera.brightness, camera.saturation, camera.contrast, imgBrightness, diff, camera.shutter_speed))

    # start converter process
    self.convert_image(now, fullname)


def settle(self, duration):
    print("Settle capturing... waiting for %0.1fs" % (duration / 1000000))
    sleep (int(duration / 1000000))


def start(self, interval):
    # initial configuration
    sensor_mode = self.config.getint('Camera', 'SensorMode')
    resolution = (self.config.getint('Camera', 'resX'),
                  self.config.getint('Camera', 'resY'))

    # manually set the timeout, necessary for long exposures
    PiCamera.CAPTURE_TIMEOUT = self.config.getint('Camera', 'ExposureTimeout')
    # derive the frame rate range from longest exposure
    fr = (Fraction(1, int(self.config.getint('Night', 'MaxExposure')/1000000)), Fraction(1, 1))

    # shoot images
    with PiCamera(framerate_range=fr, resolution=resolution,
                  sensor_mode=sensor_mode) as camera:
        while True:
            # abort if stop file exists
            if self.stopfile.is_file():
                print ("Stopping timelapse")
                break
            else:
                # single shot
                self.single_shot(interval, camera)

        # sleeping a while until everything is settled
        self.settle(1.5*camera.exposure_speed)

    if self.stopfile.is_file():
        # we are finished
        os.remove(self.stopfile)

if name == "main":
service = TimelapseService()
service.start(service.config.getint('Camera', 'interval')) `

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions