
AFEngine C++ library
v3.0.3
Table of contents
- Overview
- Versions
- Library files
- Focus factor calculation
- AFEngine class description
- Data structures
- Build and connect to your project
- Example
- Test application
- Portability
Overview
AFEngine C++ library provides auto focus function for different software lens controllers. AFEngine class doesn’t control lens directly. It just get lens state and gives what should lens controller do for reaching optimal focus position. The library provides various auto focus features: push focus, continuous auto focus (after changing zoom), auto focus timeout, autofocus sensitivity trigger, auto focus ROI change and auto focus ROI detection. The focus factor is computed with a noise-robust band-pass (difference-of-box) edge-energy measure, so it stays reliable on noisy (e.g. thermal) video without any separate noise filtering. The library has a simple interface for embedding in different software lens controllers. To ensure autofocus operation, the user must transfer each new video frame to the library, as well as transfer the current zoom and focus positions. The library depends on Frame library (provides description of video frame class). The library uses C++17 standard. The test application uses SimpleUI library (window, slider, mouse and keyboard) and VSourceFile library (video file capture).
AFEngine is general-purpose software: it is not tied to any sector or application, and the developer of a program decides what the program does with it.
Versions
Table 1 - Library versions.
| Version | Release date | What’s new |
|---|---|---|
| 1.0.0 | 02.11.2023 | First version. |
| 1.1.0 | 25.04.2024 | - Code structure updated. - Documentation updated. |
| 1.1.1 | 23.04.2024 | - Submodules updated. - Documentation updated. |
| 1.2.1 | 29.07.2024 | - CMake updated. - Description updated. - Data structures updated. |
| 2.0.0 | 23.08.2024 | - Add calibration procedure. |
| 2.0.1 | 16.09.2024 | - Fix calibration procedure. - Add AF stuck avoidance. |
| 2.1.0 | 12.03.2025 | - Add noise reduction. - Add zero focus factor value (30). |
| 2.1.1 | 14.05.2025 | - Zero focus factor set to (10). - Fix continues mode Interruption. |
| 2.1.2 | 28.03.2026 | - Denoiser submodule updated. |
| 3.0.0 | 24.06.2026 | - Focus factor replaced with noise-robust band-pass (difference-of-box) measure. - Denoiser / noise reduction removed. - FF_DECREASE_THRESHOLD parameter removed (now a fixed internal constant applied to the focus-factor signal). - Autofocus peak detection made fully adaptive (no fixed drop threshold). - FOCUS_POSITION_ACCURACY default set to 0.5% of the full range. - CAF refocus timeout fixed: the period is now measured from autofocus finish and no longer adds a fixed re-settle delay, so it matches CAF_REFOCUS_TIMEOUT_SEC. - Test application reworked to drive the full AFEngine (AFEngineTest); the ROI is now selected with the mouse (left-button drag to draw and set it) instead of the WASD/QE keys, it shows the AFEngine per-frame compute time, and it can record the displayed video (with the ROI overlay) to afengine_<date>_<time>.mp4 with the R key. - Bug fixes (findRoi ROI position, ROI bounds, getParam not-found return value). |
| 3.0.1 | 13.07.2026 | - Restored the FF_DECREASE_THRESHOLD parameter and the relative-drop autofocus stop criterion. |
| 3.0.2 | 12.10.2026 | - Submodules updated. |
| 3.0.3 | 16.10.2026 | - The Frame library is not a sub-repository any more: when AFEngine is the top-level project it is taken from the folder next to the repository. - Test application: OpenCV replaced with the SimpleUI (window, slider, mouse, keyboard) and VSourceFile (video file capture) libraries, video recording removed, –selftest mode with automatic tests added. - setParam rejects NaN, infinity and values outside of the ranges of Table 3 (the minimum of AUTO_ROI_WIDTH and AUTO_ROI_HEIGHT is 8). - addVideoFrame returns FALSE for a frame that is not accepted (size, pixel format, no data, data smaller than the frame needs) and for a failed memory allocation. - updateData returns 0 for a zoom or focus position outside of 0 - 65535. - Fixed: out-of-bounds reads in the calibration (limits of the calibration range, time adjustment), uninitialized variable of the auto ROI search, data race of the CAF counter, calibration range outside of the lens range. - Lens history and calibration points are bounded. - Compiler warnings fixed, hardening options of the compiler and linker added to the CMake files, the unused optimization options removed from the CMake file of the library. - Documentation corrected. |
Library files
The library is supplied only by source code. The user is given a set of files in the form of a CMake project (repository). The repository structure is shown below:
CMakeLists.txt ------------ Main CMake file of the library.
src ----------------------- Folder with source code of the library.
CMakeLists.txt -------- CMake file of the library.
AFEngine.cpp ---------- C++ implementation file.
AFEngine.h ------------ Header file which includes AFEngine class declaration.
AFEngineVersion.h ----- Header file which includes version of the library.
AFEngineVersion.h.in -- CMake service file to generate version file.
FocusDetector.h ------- Header file of focus detector implementation.
FocusDetector.cpp ----- C++ implementation file of focus detector.
test ---------------------- Folder with the test application.
CMakeLists.txt -------- CMake file of the test application.
main.cpp -------------- Source code of the AFEngineTest application.
static -------------------- Folder with the pictures of this document and the video files of the test application (thermal.mp4, daylight.mp4).
Focus factor calculation
The focus factor (parameter FOCUS_FACTOR) is the value the library maximizes to reach optimal focus. It is calculated for the auto focus ROI of the most recently added video frame. AFEngine uses a band-pass edge-energy measure, implemented as the difference of two box blurs (a fast box-blur approximation of a Difference-of-Gaussians). For every pixel of the ROI two local averages of the grayscale image are computed - a small one (inner box) and a large one (outer box) - and the focus factor is the mean squared difference between them over the ROI:
d(x, y) = avg_inner(x, y) - avg_outer(x, y) // band-pass response
focus factor = FF_ZERO + ( 1 / N ) * Σ d(x, y)^2 // N = number of ROI pixels
where FF_ZERO is a small constant offset (so the value is always > 0). The inner box size is 7×7 pixels and the outer box size is 15×15 pixels by default. The measure works because the difference of two blurs keeps only a band of spatial frequencies:
| Frequency band | Removed / kept by | Effect |
|---|---|---|
| High frequencies (sensor noise) | removed by the inner box | noise does not inflate the focus factor (important for thermal video) |
| Low frequencies (overall brightness, soft bright blobs, illumination gradients) | removed by the outer box | the focus factor does not depend on brightness / contrast |
| Mid frequencies (real sharp edges) | kept in the difference | maximal exactly when the image is in focus |
As a result the focus factor is robust to noise and invariant to brightness / contrast changes, so it does not need any separate noise-reduction filtering and does not get fooled by bright but blurry frames.
Properties:
- Noise robustness - built into the measure (the inner box suppresses high-frequency noise).
- Brightness / contrast invariance - the outer box removes the low-frequency component.
- Performance -
O(N)per frame: the two box averages are obtained from a separable sliding window (incremental per-column sums plus a horizontal running sum) using small int32 buffers with sequential, cache-friendly access. No floating-point square roots, no external filtering. - Value range - relative units that depend on the ROI content; always > 0. Only the relative maximum matters for selecting the best-focus frame and for the continuous auto focus logic, not the absolute value.
Automatic ROI position check. The band-pass needs a border of real pixels (equal to the outer-box radius, 7 pixels) around every ROI pixel. If the user places the auto focus ROI closer than that to a frame edge, the region actually used for the calculation is automatically reduced (clamped to stay at least the border distance away from each edge) instead of sampling out-of-frame pixels - this keeps the focus factor correct near the frame borders. The auto-ROI detection (ROI_MODE == 1) likewise never places the detected ROI closer than the border distance to a frame edge. The auto-ROI detection of the “optimal” ROI position (ROI_MODE == 1) uses a separate gradient (Sobel) energy measure to locate the most textured region of the frame; the band-pass measure above is then used to track focus for the selected ROI.
Autofocus search. The push autofocus (command START, parameter MODE == 1) moves the lens and tracks the running maximum of the focus factor. The state machine works as follows:
-
On the start the engine remembers the start focus position and the focus factor at it. If the start focus position is in the far half of the range (more than 32767) the lens moves FAR first (action 1 of updateData), otherwise NEAR (action 3).
-
During the movement the engine takes the focus factor of the last frame together with the focus position that the frame corresponds to (the position is taken back by the TIME_ADJUSTMENT parameter) and keeps the maximum.
-
The direction is considered checked when the focus factor stops growing and falls below FF_DECREASE_THRESHOLD (default 80%) of the maximum, or when the end of the focus range is reached. This relative-drop rule does not need an absolute threshold, so it works on low-contrast scenes too. The position of the maximum is refined to sub-step accuracy by parabolic interpolation of the three samples around it.
-
If the opposite direction has not been checked and the focus factor at the start position is not below FF_DECREASE_THRESHOLD of the maximum, the engine returns the lens to the start position (action 2, the position is reqPos) and checks the opposite direction. Otherwise it goes directly to the optimal position (action 2).
-
When the lens is at the optimal position (equal to it, or closer than FOCUS_POSITION_ACCURACY while the focus does not move) the engine returns 0 and the autofocus is finished. In the continuous mode (MODE == 2) the engine goes back to the check of the refocus conditions. A running autofocus is stopped if the focus does not move during 100 calls of updateData in a row.
The autofocus may stop at a local maximum of the focus factor if the scene has several of them.
AFEngine class description
AFEngine class declaration
AFEngine class declared in AFEngine.h file. Class declaration:
namespace rapidpixel
{
/// Auto Focus Engine class.
class AFEngine
{
public:
/// Default constructor.
AFEngine();
/// Class destructor.
~AFEngine();
/// Copy and move operations are deleted.
AFEngine(const AFEngine&) = delete;
AFEngine& operator=(const AFEngine&) = delete;
AFEngine(AFEngine&&) = delete;
AFEngine& operator=(AFEngine&&) = delete;
/// Get engine version.
static std::string getVersion();
/// Add video frame for processing.
bool addVideoFrame(rapidpixel::Frame& frame);
/// Update data to get AF State Machine value.
int updateData(int zoomPos, int focusPos, int& reqPos);
/// Set the AFEngine param.
bool setParam(AFParam id, float value);
/// Get the AFEngine param.
float getParam(AFParam id);
/// Execute command.
bool executeCommand(AFCommand id);
};
}
getVersion method
The getVersion() static method returns string of current version of AFEngine class. Method declaration:
static std::string getVersion();
Method can be used without AFEngine class instance:
std::cout << "AFEngine class version: " << rapidpixel::AFEngine::getVersion() << std::endl;
Console output:
AFEngine class version: 3.0.3
addVideoFrame method
The addVideoFrame(…) method adds video frame to library for focus factor calculation. The frame is copied. To ensure autofocus operation, the user must transfer each new video frame to the library, as well as transfer the current zoom and focus positions. The library calculates the focus factor for each frame of the video. The focus factor calculation is performed in the region of interest set in the parameters (AFParam enum). The method can be called from any thread, also while another thread calls other methods of the class.
bool addVideoFrame(rapidpixel::Frame& frame);
| Parameter | Value |
|---|---|
| frame | Frame class object. The library support RAW pixel formats: BGR24, RGB24, GRAY, NV12, NV21, YU12, YV12, UYVY, YUYV and YUV24. Minimum width / height of video frame is 32. Maximum width / height of video frame is 8192. The data pointer must not be nullptr and the data size (size field of the frame) must not be less than the size that the pixel format needs for the width and height of the frame. The library can not check that the memory of the frame is valid for its data size: it is the contract of the caller. |
Returns: TRUE if frame has been added or FALSE if not: the width or the height is out of the range, the pixel format is not supported, the data pointer is nullptr, the data size is less than the frame needs, or the memory for the copy of the frame could not be allocated. If FALSE is returned the previously added frame stays in use.
updateData method
The updateData(…) method intended to set current lens state (zoom and focus position) and gives what should lens controller do for reaching optimal focus position. The library controls auto focus mode (off, push, continuous). The principle of the library is as follows: every time the zoom or focus position changes, the user must call the updateData(…) method. In this case, the method will return the code of the action that must be performed by the lens controller to implement the autofocus function. It is up to the lens controller whether the same command is sent multiple times. The method must not be called from several threads at the same time (it is the method of the lens controller thread). Other methods of the class can be called from other threads while the method runs.
int updateData(int zoomPos, int focusPos, int& reqPos);
| Parameter | Value |
|---|---|
| zoomPos | Current zoom position from controller. Should use zoom position value: 0 (full wide) - 65535 (full tele). Lens controller must convert native lens zoom position range to [0:65535]. A value outside of the range is rejected (see Returns). |
| focusPos | Current focus position from controller. Should use focus position value: 0 (full near) - 65535 (full far). Lens controller must convert native lens focus position range to [0:65535]. A value outside of the range is rejected (see Returns). |
| reqPos | Reference to required focus position. The lens controller should move lens to this position. The value is set only if the returned action is 2. |
Returns: Action ID should be done in Lens controller.
| Return value | Description |
|---|---|
| 0 | Nothing to do. The method also returns 0 (and does not change the state of the engine) if the zoom or the focus position is outside of the range [0:65535], and always if MODE is 0. |
| 1 | Go to focus FAR. The movement speed must be decided by lens controller to provide necessary auto focus accuracy. Fast movement can lead to not accurate auto focus. |
| 2 | Go to “start” focus position as fast as possible. Necessary focus position is reqPos. “Start” focus position is the position of auto focus start. |
| 3 | Go to focus NEAR. The movement speed must be decided by lens controller to provide necessary auto focus accuracy. Fast movement can lead to not accurate auto focus. |
setParam method
The setParam(…) method sets AFEngine parameters value. Method declaration:
bool setParam(AFParam id, float value);
| Parameter | Description |
|---|---|
| id | AFEngine parameter ID according to AFParam enum. |
| value | AFEngine parameter value. |
Returns: TRUE if the parameter was set or FALSE if not: the parameter is read only (AUTO_ROI_X0, AUTO_ROI_Y0, FOCUS_FACTOR), the ID is unknown, or the value is NaN or outside of the range of the parameter (see Table 3). A rejected value does not change the parameter. The method can be called from any thread.
getParam method
The getParam(…) method designed to obtain AFEngine parameter value. Method declaration:
float getParam(AFParam id);
| Parameter | Description |
|---|---|
| id | AFEngine parameter ID according to AFParam enum. |
Returns: parameter value or -1 if the parameter doesn’t exist. The value of FOCUS_FACTOR is -1 also if the focus factor can not be calculated (no video frame has been added, the ROI is outside of the valid frame area, the ROI size is not positive or the memory for the calculation could not be allocated). The method can be called from any thread.
executeCommand method
The executeCommand(…) method designed to execute AFEngine action command.
bool executeCommand(AFCommand id);
| Parameter | Description |
|---|---|
| id | AFEngine action command ID according to AFCommand enum. |
Returns: TRUE if the command executed or FALSE (unknown command ID). The method can be called from any thread.
Data structures
AFCommand enum
The AFCommand enum declared in AFEngine.h file:
enum class AFCommand
{
/// Start auto focus.
START,
/// Stop auto focus.
STOP,
/// Calibrate auto focus engine.
CALIBRATE
};
Table 2 - AFEngine action commands description.
| Command | Description |
|---|---|
| START | Start autofocus. Push focus. |
| STOP | Stop autofocus. |
| CALIBRATE | Calibrate auto focus engine. Measure time delay between lens position and corresponding frame and set TIME_ADJUSTMENT parameter of the AFParam enum. Typically the video from the camera comes later than the zoom or focus position from the lens controller. That is, there is a delay between the data and the focus or zoom position and the corresponding video frame. This delay affects the accuracy of autofocus (the longer the delay, the less accurate the autofocus). To reduce the effect of time delay between the zoom and focus positions and the video frame, AFEngine has a built-in calibration algorithm that is triggered by user command. Calibration is usually performed once for a particular camera and lens. To perform a calibration, the user must point the camera at a contrasting object, adjust the focus so that the object is contrasting in the video and run the calibration. AFEngine will then perform a series of actions to calculate the TIME_ADJUSTMENT parameter of the AFParam enum. |
AFParam enum
AFParam enum declared in AFEngine.h file:
enum class AFParam
{
/// Auto focus mode: 0 - Off, 1 - PUSH AF, 2 - CAF.
/// Values: 0, 1 or 2.
/// (write/read)
MODE,
/// Auto focus ROI top-left corner horizontal position in pixels.
/// Auto focus ROI is rectangle. Value: from 0 to 8192.
/// (write/read)
ROI_X0,
/// Auto focus ROI top-left corner vertical position in pixels.
/// Auto focus ROI is rectangle. Value: from 0 to 8192.
/// (write/read)
ROI_Y0,
/// Auto focus ROI bottom-right corner horizontal position in pixels.
/// Auto focus ROI is rectangle. Value: from 0 to 8192.
/// (write/read)
ROI_X1,
/// Auto focus ROI bottom-right corner vertical position in pixels.
/// Auto focus ROI is rectangle. Value: from 0 to 8192.
/// (write/read)
ROI_Y1,
/// Auto focus auto ROI top-left corner vertical position in pixels.
/// Auto focus ROI is rectangle. The value is -1 until the ROI is
/// detected.
/// (read)
AUTO_ROI_Y0,
/// Auto focus auto ROI top-left corner horizontal position in pixels.
/// Auto focus ROI is rectangle. The value is -1 until the ROI is
/// detected.
/// (read)
AUTO_ROI_X0,
/// ROI width (pixels) for auto focus algorithm when lens controller
/// detects ROI position automatically. Value: from 8 to 8192, but the
/// ROI must fit in the video frame: not more than (video frame width -
/// AUTO_ROI_BORDER * 2).
/// (write/read)
AUTO_ROI_WIDTH,
/// ROI height (pixels) for auto focus algorithm when lens controller
/// detects ROI position automatically. Value: from 8 to 8192, but the
/// ROI must fit in the video frame: less than (video frame height -
/// AUTO_ROI_BORDER * 2).
/// (write/read)
AUTO_ROI_HEIGHT,
/// Video frame border size (along vertical and horizontal axes).
/// Value: from 0 to 2048, but not more than a quarter of the video frame
/// width and height. A border smaller than 7 pixels is raised to 7
/// pixels. The default value -1 means the minimum border.
/// (write/read)
AUTO_ROI_BORDER,
/// AF ROI mode. Value: 0 - Manual position, 1 - Auto position.
/// (write/read)
ROI_MODE,
/// Focus factor threshold for CAF mode. Value 0-100 (%).
/// (write/read)
CAF_FF_THRESHOLD,
/// AF refocus timeout sec in CAF mode. 0 - no refocus by timeout.
/// Value: from 0 to 100000.
/// (write/read)
CAF_REFOCUS_TIMEOUT_SEC,
/// Focus factor value.
/// (read)
FOCUS_FACTOR,
/// Threshold to detect FOCUS moving, in units of the focus position.
/// Depends on implementation. Value: from 0 to 65535.
/// (write/read)
FOCUS_MOVE_THRESHOLD,
/// Threshold to detect ZOOM moving, in units of the zoom position.
/// Depends on implementation. Value: from 0 to 65535.
/// (write/read)
ZOOM_MOVE_THRESHOLD,
/// Time adjustment for AF procedure, ms.
/// Basically frame come with some delay relative to the position.
/// For optimal focus factor taken past position.
/// Value: from 0 to 10000.
/// (write/read)
TIME_ADJUSTMENT,
/// FOCUS position accuracy to detect position reached
/// in case focus position feedback is not accurate.
/// AFEngine decide that required position reached if FOCUS stop moving
/// (according FOCUS_MOVE_THRESHOLD option) and
/// current position differ from required less than
/// FOCUS_POSITION_ACCURACY. Value: from 0 to 65535.
/// Default: 0.5% of the full 0..65535 range (~328).
/// (write/read)
FOCUS_POSITION_ACCURACY,
/// Focus factor decreasing threshold. Value 0-100 (%).
/// In AF procedure switch focus moving direction if
/// MAX FOCUS_FACTOR * FF_DECREASE_THRESHOLD > CURRENT FOCUS_FACTOR.
/// Default 80%.
/// (write/read)
FF_DECREASE_THRESHOLD
};
Table 3 - Auto focus engine params description.
| Parameter | Access | Description |
|---|---|---|
| MODE | read / write | Autofocus mode: 0 - Off (method updateData(…) always returns 0), 1 - Push AF (doing auto focus algorithm only by command), 2 - Continuous AF (start autofocus after zoom changed). Values: 0, 1 or 2. |
| ROI_X0 | read / write | Autofocus ROI top-left corner horizontal position, pixels. Initial auto focus ROI position must be set by user in advance according to frame size. Value: from 0 to 8192. |
| ROI_Y0 | read / write | Autofocus ROI top-left corner vertical position, pixels. Initial auto focus ROI position must be set by user in advance according to frame size. Value: from 0 to 8192. |
| ROI_X1 | read / write | Autofocus ROI bottom-right corner horizontal position, pixels. Initial auto focus ROI position must be set by user in advance according to frame size. Value: from 0 to 8192. |
| ROI_Y1 | read / write | Autofocus ROI bottom-right corner vertical position, pixels. Initial auto focus ROI position must be set by user in advance according to frame size. Value: from 0 to 8192. |
| AUTO_ROI_Y0 | read only | Autofocus auto ROI top-left corner vertical position in pixels. If parameter ROI_MODE == 1 the library will detect “optimal” auto focus ROI position. The value is -1 until the ROI is detected. |
| AUTO_ROI_X0 | read only | Autofocus auto ROI top-left corner horizontal position in pixels. If parameter ROI_MODE == 1 the library will detect “optimal” auto focus ROI position. The value is -1 until the ROI is detected. |
| AUTO_ROI_WIDTH | read / write | Auto ROI width (pixels) for autofocus algorithm. By default 128. User should set preferable auto focus ROI width. If parameter ROI_MODE == 1 the library will detect “optimal” auto focus ROI position. Value: from 8 to 8192, the ROI must fit in the video frame (not more than the frame width - AUTO_ROI_BORDER * 2), otherwise the ROI is not detected. |
| AUTO_ROI_HEIGHT | read / write | Auto ROI height (pixels) for autofocus algorithm. By default 128. User should set preferable auto focus ROI height. If parameter ROI_MODE == 1 the library will detect “optimal” auto focus ROI position. Value: from 8 to 8192, the ROI must fit in the video frame (less than the frame height - AUTO_ROI_BORDER * 2), otherwise the ROI is not detected. |
| AUTO_ROI_BORDER | read / write | Auto ROI border size, pixels. Border size used to exclude video frame borders for auto ROI detection. Value: from 0 to 2048, but not more than a quarter of the video frame width and height. A border smaller than 7 pixels is raised to 7 pixels. By default -1 (the minimum border). |
| ROI_MODE | read / write | Auto focus ROI mode: 0 - Manual position (user must set ROI position), 1 - Auto position (“optimal” ROI position will be detected by library). Values: 0 or 1. |
| CAF_FF_THRESHOLD | read / write | Threshold for changes of focus factor to start refocus in Continuous AF mode: 0% - no check (the library will no control focus factor to start AF automatically), 100% - changing x2. Value: from 0 to 100. |
| CAF_REFOCUS_TIMEOUT_SEC | read / write | Timeout for automatic refocus, seconds. 0 - no automatic refocus, 100000 - maximum value. Value: from 0 to 100000. |
| FOCUS_FACTOR | read only | Focus factor value, relative units depends on image (>0). The value is -1 if the focus factor can not be calculated (see getParam method). |
| FOCUS_MOVE_THRESHOLD | read / write | Focus moving threshold in units ([0:65535] range). Value depends on lens controller. Used to detect Focus movement. Some lens controller may give us zoom and focus position with fluctuations. To avoid false focus movement detection user should set threshold (if current focus position has absolute value ([0:65535] range) more than previous +/- threshold the AFEngine will decide that focus is moving.) Value: from 0 to 65535. |
| ZOOM_MOVE_THRESHOLD | read / write | Zoom moving threshold in units ([0:65535] range). Value depends on lens controller. Used to detect Zoom movement. Some lens controller may give us zoom and focus position with fluctuations. To avoid false zoom movement detection user should set threshold (if current zoom position has absolute value ([0:65535] range) more than previous +/- threshold the AFEngine will decide that zoom is moving.) Value: from 0 to 65535. |
| TIME_ADJUSTMENT | read / write | Usually in the systems there is a latency between video frame and actual zoom and focus position obtained from lens hardware. Also there usually number of zoom and focus measurements per second more then video FPS. The library assumes that obtaining zoom and focus position faster than corresponding frame coming to AFEngine. The value means delay in ms. between current zoom/focus position and corresponding frame. May be measured by CALIBRATE command. Value: from 0 to 10000. |
| FOCUS_POSITION_ACCURACY | read / write | Focus position accuracy to detect position reached in units ([0:65535] range). It used to detect when lens reached particular position. In case focus position feedback is not accurate AFEngine decide that required position reached if FOCUS stop moving (according FOCUS_MOVE_THRESHOLD option) and current position differ from required less than FOCUS_POSITION_ACCURACY. Value: from 0 to 65535. Default: 0.5% of the full 0-65535 range (65535 × 0.005 ≈ 328). |
| FF_DECREASE_THRESHOLD | read / write | Focus factor decreasing threshold, 0-100 (%). During the autofocus sweep the moving direction is reversed (peak considered passed) as soon as the current focus factor drops below this percentage of the maximum focus factor seen so far. Default 80%. Lower values make the peak detection more tolerant to noise; higher values stop the sweep sooner. Value: from 0 to 100. |
Build and connect to your project
Build stand alone
The repository does not contain sub-repositories. The Frame library that AFEngine depends on must be cloned into the same folder as AFEngine. The test application additionally needs the SimpleUI and VSourceFile libraries and the libraries that VSourceFile depends on: ConfigReader, VCodec, VSource, FormatConverter, VDecoderH264, VDecoderH265 and VDecoderJPEG. The folder structure must be:
<work folder>
AFEngine
Frame
ConfigReader
VCodec
VSource
FormatConverter
VDecoderH264
VDecoderH265
VDecoderJPEG
VSourceFile
SimpleUI
Typical commands to build the library and the test application (use the URLs of the repositories provided to you):
git clone <AFEngine repository URL>
git clone <Frame repository URL>
git clone <ConfigReader repository URL>
git clone <VCodec repository URL>
git clone <VSource repository URL>
git clone <FormatConverter repository URL>
git clone <VDecoderH264 repository URL>
git clone <VDecoderH265 repository URL>
git clone <VDecoderJPEG repository URL>
git clone <VSourceFile repository URL>
git clone <SimpleUI repository URL>
cd AFEngine
mkdir build
cd build
cmake ..
make
On Windows use cmake --build . --config Release instead of make. If AFEngine is the top-level CMake project, the main CMakeLists.txt file adds the folder ../Frame before the library itself. If the test application is built, it also adds the folders ../ConfigReader, ../VCodec, ../VSource, ../FormatConverter, ../VDecoderH264, ../VDecoderH265, ../VDecoderJPEG, ../VSourceFile and ../SimpleUI (in this order) before the test application. If a folder does not exist CMake stops with an error. If you do not need the test application, switch it off (see CMake options): only the Frame folder is needed then.
CMake options
Table 4 - CMake options of the repository. In the options below ${PARENT} is the prefix defined by the top-level project (empty if AFEngine is the top-level project).
| Option | Default | Description |
|---|---|---|
| ${PARENT}_AF_ENGINE | ON | Build the library. |
| ${PARENT}_AF_ENGINE_TEST | ON (stand alone), OFF (included in other project) | Build the test application. |
| ${PARENT}_SUBMODULE_CACHE_OVERWRITE | ON | Forced rewriting of the options above in the CMake cache. The top-level project sets it to OFF when it includes AFEngine as a sub-repository. When AFEngine is the top-level project, set it to OFF to change the options above from the command line, for example cmake -D_SUBMODULE_CACHE_OVERWRITE=OFF -D_AF_ENGINE_TEST=OFF ... |
| CMAKE_BUILD_TYPE | Release (if not specified) | Build type. |
Connect to your project
If you want connect AFEngine library to your CMake project as source code you can make follow. For example, if your repository has structure:
CMakeLists.txt
src
CMakeList.txt
yourLib.h
yourLib.cpp
Create folder 3rdparty in your repository and copy the AFEngine and Frame repository folders there. New structure of your repository:
CMakeLists.txt
src
CMakeList.txt
yourLib.h
yourLib.cpp
3rdparty
AFEngine
Frame
Create CMakeLists.txt file in 3rdparty folder. CMakeLists.txt should contain the dependencies of AFEngine first (Frame) and then AFEngine itself:
cmake_minimum_required(VERSION 3.13)
################################################################################
## 3RD-PARTY
## dependencies for the project
################################################################################
project(3rdparty LANGUAGES CXX)
################################################################################
## SETTINGS
## basic 3rd-party settings before use
################################################################################
# To inherit the top-level architecture when the project is used as a submodule.
SET(PARENT ${PARENT}_YOUR_PROJECT_3RDPARTY)
# Disable self-overwriting of parameters inside included subdirectories.
SET(${PARENT}_SUBMODULE_CACHE_OVERWRITE OFF CACHE BOOL "" FORCE)
################################################################################
## CONFIGURATION
## 3rd-party submodules configuration
################################################################################
SET(${PARENT}_SUBMODULE_FRAME ON CACHE BOOL "" FORCE)
if (${PARENT}_SUBMODULE_FRAME)
SET(${PARENT}_FRAME ON CACHE BOOL "" FORCE)
SET(${PARENT}_FRAME_TEST OFF CACHE BOOL "" FORCE)
endif()
SET(${PARENT}_SUBMODULE_AF_ENGINE ON CACHE BOOL "" FORCE)
if (${PARENT}_SUBMODULE_AF_ENGINE)
SET(${PARENT}_AF_ENGINE ON CACHE BOOL "" FORCE)
SET(${PARENT}_AF_ENGINE_TEST OFF CACHE BOOL "" FORCE)
endif()
################################################################################
## INCLUDING SUBDIRECTORIES
## Adding subdirectories according to the 3rd-party configuration
################################################################################
if (${PARENT}_SUBMODULE_FRAME)
add_subdirectory(Frame)
endif()
if (${PARENT}_SUBMODULE_AF_ENGINE)
add_subdirectory(AFEngine)
endif()
File 3rdparty/CMakeLists.txt adds folders Frame and AFEngine to your project and will exclude test applications from compiling (by default tests excluded from compiling if AFEngine included as sub-repository). AFEngine does not add its dependencies when it is included in another project: the project must add the Frame library itself (in any order relative to AFEngine), otherwise the compiler stops with the error Frame.h: No such file or directory. Your repository new structure will be:
CMakeLists.txt
src
CMakeList.txt
yourLib.h
yourLib.cpp
3rdparty
CMakeLists.txt
AFEngine
Frame
Next you need include folder 3rdparty in main CMakeLists.txt file of your repository. Add string at the end of your main CMakeLists.txt:
add_subdirectory(3rdparty)
Next you have to include AFEngine library in your src/CMakeLists.txt file:
target_link_libraries(${PROJECT_NAME} AFEngine)
Done!
Example
Pseudo code of using AFEngine in lens controller library:
yourLens.h
#include "AFEngine.h"
***
class yourLens : public Lens
{
***
private:
rapidpixel::AFEngine m_afEngine;
***
}
yourLens.cpp
#include "yourLens.h"
***
/// Class constructor.
rapidpixel::yourLens::yourLens()
{
***
if (!m_afEngine.setParam(rapidpixel::AFParam::FOCUS_MOVE_THRESHOLD, 0) ||
!m_afEngine.setParam(rapidpixel::AFParam::ZOOM_MOVE_THRESHOLD, 0) ||
!m_afEngine.setParam(rapidpixel::AFParam::FOCUS_POSITION_ACCURACY, 350))
{
/// Place code to report the error: the value is rejected.
}
}
/// Add video frame to calculate focus factor.
void rapidpixel::yourLens::addVideoFrame(rapidpixel::Frame& frame)
{
if (!m_afEngine.addVideoFrame(frame))
{
/// Place code to report the error: the frame is not accepted
/// (unsupported pixel format, size or data).
}
}
void rapidpixel::yourLens::lensCommunicationThreadFunction()
{
***
int reqPos=0;
int afAction = 0;
int afActionPrev = 0;
***
while(true)
{
***
afAction = m_afEngine.updateData(m_params.zoomPos,
m_params.focusPos,
reqPos);
if(afAction == 0)
m_params.afIsActive = false;
else
m_params.afIsActive = true;
switch(afAction)
{
case 0: // nothing to do
break;
case 1: // move to far
if(afAction!= afActionPrev)
{
/// Place code to move lens FAR with AF_HW_SPEED
}
break;
case 2: // move to Start or optimal
if(afAction!= afActionPrev)
{
/// Place code to move lens as fast as possible to FOCUS_TO_ABS_POS = reqPos
}
break;
case 3: // move to near
if(afAction!= afActionPrev)
{
/// Place code to move lens NEAR with AF_HW_SPEED
}
break;
}
afActionPrev = afAction;
}
}
Test application
AFEngineTest is a small application that tests the AFEngine itself through its public interface. It uses the SimpleUI library for the window, the slider, the mouse and the keyboard and the VSourceFile library for reading the video file. It does not compute the focus factor on its own - it feeds video frames to the engine and obeys the focus commands the engine returns. Source code: test/main.cpp.
The video file plays the role of a lens with a fixed zoom: a focus position (0 - 65535) is mapped to a frame number (0 - frameCount-1) of the selected video. On start the user chooses the video type (thermal or daylight); the chosen file (thermal.mp4 or daylight.mp4, located in static of the repository) must be in the working directory. All frames of the file are then loaded into memory as BGR24 colour frames (VSourceFile plays the file in a loop, the application stops loading when the file starts again), so the display loop only picks the corresponding frame from the array - the video is shown at maximum speed in any mode. The loading is limited to 100000 frames and 8 GB of memory: the frames of thermal.mp4 (732 frames of 640x512) need 0.7 GB, the frames of daylight.mp4 (1936 frames of 1280x720) need 5.4 GB. Each cycle the application displays the colour frame (with overlays) but feeds a grayscale copy of it to the engine via addVideoFrame.
A Focus position slider (range 0 - 65535) simulates the focus position. While the engine is idle, moving the slider selects the corresponding frame (slider value scaled to the frame count) and reports that focus position to the engine. The ROI the engine uses is drawn as a cyan rectangle, and the engine state (focus position, frame, zoom, last AF command, focus factor, and the AFEngine per-frame compute time in microseconds) is shown over the image.
When autofocus runs, the application obeys the engine commands by moving one frame per display cycle: FOCUS FAR steps one frame forward, FOCUS NEAR one frame back, GO TO POS moves one frame towards the requested position. The current zoom position is passed to the engine every cycle (addVideoFrame + updateData). Thus “focusing” means the AFEngine stopping the focus on the sharpest frame. User interface:

Controls:
| Key | Action |
|---|---|
| 1 | Auto-select the AF ROI (ROI_MODE = 1) and run autofocus (the START command). |
| 2 | Run autofocus (push AF - START) with the current ROI. |
| Left mouse button | Drag on the video to draw a new ROI rectangle (shown in green while drawing); on release it is applied as the manual ROI (sets ROI_X0/Y0/X1/Y1). |
| Slider | Set the focus position manually while the engine is idle. |
| ESC | Exit the application. The application also exits when the window is closed. |
The whole behaviour is driven by the AFEngine state machine. With a manual ROI (key 2) on a video whose focus-factor curve has a single clear peak (e.g. thermal.mp4) the autofocus reaches the sharpest frame from any starting position. On a video whose focus-factor curve has several local maxima (e.g. daylight.mp4) the hill-climb may settle on a local peak. Key 1 auto-selects the most textured region of the current frame as the ROI before focusing, so it focuses that region (which is the one already near focus at the start).
Self-test. The application started with the –selftest argument runs automatic tests of the library through its public interface without a window and without a video file: the version, the parameters (defaults, ranges, rejected values), the accepted and rejected video frames, the focus factor, the ROI search, the commands and positions, the push and continuous autofocus and the calibration on a simulated lens, random calls with invalid values and calls from several threads at the same time. It prints one block per test and returns 0 if all tests passed or 1 if not:
AFEngineTest --selftest
Portability
The library is written in standard C++17. It has no platform specific code, no SIMD code and no inline assembly, it does not serialize data and does not depend on the byte order. It was built and tested (the self-test of the test application and the focus factor and state machine results) with MSVC 2022 (Windows x86-64), GCC 12 and 13 and Clang 15 and 18 (Linux x86-64) and GCC (Linux aarch64, run under qemu): the results are identical on all of them. The test application shows its window on Windows and on Linux with the X11 development libraries (libx11-dev, libxext-dev); on other systems SimpleUI uses a stub window that shows nothing.