0

I am using the python picamera module developed by Dave Hughes. As per the documentation, the camera exposure can be set between -25 to 25. In my application, I need to get brightest pixels from the image being captured, hence I am working through this by exposure correction method. From the average value of the Value channel of the image captured, I am comparing it with a threshold, and if it is greater than the threshold.

import cv2
import picamera
THRESHHOLD = 60
with picamera.Picamera() as camera:
    camera.resolution = (640, 480)
    camera.framerate = 24
    stream = io.BytesIO()
    while True:
        camera.capture(stream, format="jpeg", use_video_port=True)
        frame = np.fromstring(stream.getvalue(), dtype=np.uint8)
        stream.seek(0)
        frame = cv2.imdecode(frame, 1)
        hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
        h, s, v = cv2.split(hsv)
        average = np.average(v)
        if average > THRESHHOLD:
            camera.exposure_compensation -= int((average - THRESHHOLD)/10)

Am I doing it right?

1

First, I'd query what it is you're trying to accomplish with exposure_compensation. By default, the camera runs an AGC algorithm (when exposure_mode is set to anything except 'off') which does a pretty good job of figuring out the exposure time. As I understand it, the exposure_compensation property is used when you want to deliberately under or over expose the resulting image. Generally the best way to get a decent exposure time is to insert a delay for a few frames after initializing the camera (this is why most of my examples include something like time.sleep(2) after init; 2 seconds is complete overkill but it gets the point across that some delay is usually required).

Still, leaving aside the question of "why?" for a second, let's deal with the "how". I don't see anything wrong with your code, but we can probably speed it up a bit with a couple of tricks. Firstly, consider the stages that your capture is passing through:

  1. Camera captures an image in YUV format
  2. Camera encodes the YUV data as JPEG and sends it to your script
  3. OpenCV decodes the JPEG into BGR
  4. OpenCV converts the BGR to HSV
  5. Numpy is used to average the V values from the HSV array

It's pretty obvious from the above that the JPEG compression and decompression is redundant. We could get rid of that step by capturing with the unencoded bgr format straight off:

import picamera
import picamera.array
import cv2
import numpy as np
import time

THRESHOLD = 60

with picamera.PiCamera() as camera:
    camera.resolution = (640, 480)
    camera.framerate = 24
    # Give the AGC some warmup time
    time.sleep(0.1)
    with picamera.array.PiRGBArray(camera) as stream:
        camera.capture(stream, format='bgr', use_video_port=True)
        hsv = cv2.cvtColor(stream.array, cv2.COLOR_BGR2HSV)
        h, s, v = cv2.split(hsv)
        average = np.average(v)
        if average > THRESHOLD:
            camera.exposure_compensation -= int((average - THRESHOLD) / 10)

However, we might be able to go further depending on whether the luminance channel in a YUV capture (the Y bit) is close enough to the V values in the HSV representation (this is something you'll have to decide for yourself). If it is, then we can dispense with OpenCV entirely:

import picamera
import picamera.array
import numpy as np
import time

THRESHOLD = 60

with picamera.PiCamera() as camera:
    camera.resolution = (640, 480)
    camera.framerate = 24
    # Give the AGC some warmup time
    time.sleep(0.1)
    with picamera.array.PiYUVArray(camera) as stream:
        camera.capture(stream, format='yuv', use_video_port=True)
        # YUV data can be accessed in stream.array and stream.array[..., 0]
        # will return a 2D array of just the Y values
        average = np.average(stream.array[..., 0])
        if average > THRESHOLD:
            camera.exposure_compensation -= int((average - THRESHOLD) / 10)

Finally, I'd note that exposure_compensation works in increments of 1/6th of a stop (so setting it to 6 increases exposure by 1 stop). I'm not sure how the algorithm proposed at the end (average-60)/10 equates to 1/6ths of a stop. The range of possible values that can be produced by that algorithm are -6 (when average is 0) to 19 (when average is 255); that's certainly within the allowable range of values (-25 to +25), but what do the values represent?

Oh, one other quick thought: you might want to experiment with the shutter_speed property for adjusting exposures (this is what the AGC algorithm fiddles with by default). There'll be a recipe in the forthcoming 1.7 version which deals with locking down the settings on the camera for consistent shooting too.

  • I get Incorrect buffer length for resolution %dx%d' % (width, height)) picamera.exc.PiCameraValueError: Incorrect buffer length for resolution 640x480 error Why is so? – tilaprimera Aug 12 '14 at 16:10
  • I need to use exposure compensation so that brighest pixels are seen the brightest and can be spotted out easily. – tilaprimera Aug 12 '14 at 17:08
  • On the incorrect buffer length error, are you using the resize parameter in the call to capture? – Dave Jones Aug 15 '14 at 11:57
  • No, the resize parameter in the call is not being used for capture. – tilaprimera Aug 18 '14 at 11:41
  • Are you capturing in a loop? If so, you need to clear down the stream between captures. The PiRGBArray docs cover doing this with truncate. – Dave Jones Aug 19 '14 at 0:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.