picamera2 icon indicating copy to clipboard operation
picamera2 copied to clipboard

[HOW-TO] Autofocus

Open tennisparty opened this issue 2 years ago • 3 comments

Hello,

I am trying to use AfWindows to focus the camera on different parts of the window. I need a bit of help translating the regions of interest displayed on my preview, to the ScalarCropMaximum size. The ScalarCropMaximum is used to set the focus area in AfWindows according to the picamera2 docs. Based on this I assume that I need to scale the preview size to the active window in the ScalarCropMaximum and then translate it by the ScalarCropMaximum x and y offset. I have attempted to do this in the function AccountForScalerCrop in my script. This doesn't seem to be working, am I translating or scaling the image in the wrong way?

I have tried writing an example script

import time
sys.path.append('/usr/lib/python3/dist-packages/')
from picamera2 import Picamera2, Preview, MappedArray
from libcamera import controls
picam2 = Picamera2()
import cv2
import numpy as np

# Set display size and create variables for later
lores = (1280, 800)
width, height = lores

# start cam
picam2.start_preview(Preview.DRM, x=100, y=100, width=1280, height=800)
preview_config = picam2.create_preview_configuration(main={"size": lores})
picam2.configure(preview_config)
picam2.start()

# print properties - including scalar crop max
properties = picam2.camera_properties
print(properties)

# print metadata
metadata = picam2.capture_metadata()
print(metadata)

# set afmetering to windows so it can be called
picam2.controls.set_controls( { "AfMetering" : controls.AfMeteringEnum.Windows} )

# turn scalercropmax into a variable
scaler_crop_maximum = picam2.camera_properties['ScalerCropMaximum']

# set some example ROI
coord1 = (0,0, width // 2, height)
coord2 = (width // 2, height // 4, width // 2, height // 2)
coord3 = (0, height // 4, width // 2, height // 2)
coord4 = (100, 100, width, height)
coord5 = (1000,100, 500, 200)

# function for printing variables consistently
def print_coordinates(coordinates, name):
    print(name.upper())
    print(f"x_offset = {coordinates[0]}")
    print(f"y_offset = {coordinates[1]}")
    print(f"width = {coordinates[2]}")
    print(f"height = {coordinates[3]}")

# function to attempt to scale variables designed for preview into scalercropmax size for AfWindows
def AccountForScalerCrop(coord, scaler_crop_maximum, lores):
    x_offset, y_offset, width, height = coord
    print_coordinates(coord, "coord")
    
    # scaler_crop_maximum represents a larger image than the image we use so we need to account
    x_offset_scm, y_offset_scm, width_scm, height_scm = scaler_crop_maximum
    print_coordinates(scaler_crop_maximum, "SCM")

    # create a scale so that you can scale the preview to the SCM
    yscale = int(scaler_crop_maximum[3]) / int(lores[1])
    xscale = int(scaler_crop_maximum[2]) / int(lores[0]) 
    print("yscale, xscale",yscale, xscale)

    # scale coords to SCM
    y_offset_scaled = int(y_offset * yscale)
    height_scaled = int(height * yscale)
    x_offset_scaled = int(x_offset * xscale)
    width_scaled = int(width * xscale)

    coordscaled = (x_offset_scaled, y_offset_scaled, width_scaled, height_scaled)
    print_coordinates(coordscaled, "coordscaled")

    adjustedcoord = (x_offset_scm + x_offset_scaled, y_offset_scm + y_offset_scaled, width_scaled, height_scaled)
    print_coordinates(adjustedcoord, "adjustedcoord")

    return adjustedcoord

# function to focus cam based on scaled coords and draw overlays for coords
def focusarea(coord, scaler_crop_maximum, lores):
    adjustedcoord = AccountForScalerCrop(coord, scaler_crop_maximum, lores)
    picam2.set_controls({"AfWindows": [adjustedcoord]})
    print("focusing cam")
    job = picam2.autofocus_cycle(wait=False)
    # Now do some other things, and when you finally want to be sure the autofocus cycle is finished:
    success = picam2.wait(job)
    # query lens position after completion
    LensPosition = picam2.capture_metadata().get("LensPosition")
    print("LENS POSITION:", LensPosition)
    
    x_offset, y_offset, width, height = coord

    # create overlay the size of the preview
    overlay = np.zeros((lores[1], lores[0], 4), dtype=np.uint8)
    overlay[y_offset:height+y_offset,x_offset:width+x_offset] = (0, 255, 0, 64)

    picam2.set_overlay(overlay)
    time.sleep(5)

# create list of coordinates
coordinates = [coord1, coord2, coord3, coord4, coord5]
# loop through coordinates
for i, coord in enumerate(coordinates, start=1):
    print(f">>>>>>>>FOCUSING COORD{i}")
    focusarea(coord, scaler_crop_maximum, lores)

print("script finished")```



tennisparty avatar Jan 08 '24 08:01 tennisparty

@njhollinghurst Nick, could you maybe comment on this question? Thanks!

davidplowman avatar Jan 08 '24 09:01 davidplowman

The libcamera controls spec is a little ambiguous here, but I've always interpreted it to mean "raw" coordinates in the sensor's active pixel array. That is to say, the crop X,Y starting position should be added (not subtracted).

If that's the case then this code looks correct (at first sight), except that yscale and xscale are generally not integers.

@tennisparty How is it going wrong?

njhollinghurst avatar Jan 08 '24 20:01 njhollinghurst

Hi Nick and David,

Thanks for this, I have had another look this morning. It turns out I hadn't set the AfMode before calling the autofocus. I have setup a FoV for the camera with multiple focal distances and whenever the display overlay changes the focus now matches that area, success!

Thanks again for your help, Sam

tennisparty avatar Jan 09 '24 08:01 tennisparty