[HOW-TO] Autofocus
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")```
@njhollinghurst Nick, could you maybe comment on this question? Thanks!
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?
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