vhliboptimal

Language CMake Platform License Version


Header Description
Project VHLibOptimal
Description C++17 library for shape contour detection and image outline recognition
Current Version 0.7.5-beta (2026)
Development started 2006
Major C++17 rewrite started in early 2026
Author V01G04A81 / Viktor Glebov
License MIT
Source code https://github.com/vigatron/vhliboptimal


A high-performance C++17 library for fast shape contour detection and image outline recognition using optimized grid-based scanning.

img

Historical reference: The 2016 FPGA-based stereo vision system that proved the algorithm’s real-time viability on dual-camera setups.

The core algorithm, originally developed in 2006, received a complete modern C++17 rewrite in 2026. This version brings a clean object-oriented interface for single-camera Single Board Computer setups while preserving two decades of embedded efficiency lessons.


Project Overview

vhliboptimal is a high-performance C++ library for fast shape contour detection and image outline recognition.

Originally developed in plain C (starting in 2006) for commercial embedded projects on ARM and AVR platforms.

Later evolved through an FPGA-accelerated era (2016).

It has been completely modernized in 2026 with a clean object-oriented C++ interface while preserving its efficiency-focused philosophy.

It uses an optimized grid-based approach: the image is divided into a configurable Cells Matrix, and connectivity is tracked using compact BitFields.

This design delivers excellent performance with very low memory and CPU usage, making it ideal for embedded systems and real-time applications.

Unlike general-purpose computer vision frameworks such as OpenCV, vhliboptimal focuses exclusively on contour extraction and therefore remains lightweight and easy to integrate.

It excels at processing binary or high-contrast images and gracefully handles small gaps and noise thanks to tunable parameters.


Key Features


Road Signs Recognition Example

The examples below demonstrate how vhliboptimal is utilized within a real-world road sign recognition application.

In this specific pipeline, the library is responsible exclusively for the high-speed, deterministic extraction of shape contours and internal spans from pre-processed frames. The extracted geometric data is then passed to a higher-level classification module.

Example #1: Road signs (front view)
src flt fin
flr flb flc
Example #2: Road signs (view from angle)
src flt fin
flr flb flc
Example #3: Geometric Shapes
original result
Example #4: Text Localization (character coordinates & sizes)
original result

Note: Original image processed at 1080p (contains > 2000 character objects). Only positions and sizes are extracted — content is not recognized.


History & Evolution

The vhliboptimal library has deep roots in real-world embedded computer vision, evolving from extreme hardware constraints to modern software efficiency.

2006 — The Extreme Embedded Roots (AVR + External SRAM)

The algorithm originated as a raster-to-vector engine for 8-bit AVR microcontrollers, initially tasked with recognizing character contours and geometric shapes on tiny 128x64 B&W displays. To handle image processing under severe memory constraints, the system utilized 32KB of external SRAM accessed via a multiplexed bus (74HC573 + ALE). The core engineering challenge was overcoming the performance bottleneck of this external memory bus.

2010 … 2012 — Road Signs Recognition

The grid-based BitField architecture was specifically designed during this period to minimize external bus accesses, keeping the heavy pathfinding logic strictly within the MCU’s fast internal RAM. It was successfully tested on LPC2148 and AT91SAM7X256 platforms.

2016 — Hardware-Accelerated Era (FPGA + STM32)

As tasks grew more complex, the algorithm was scaled and integrated into a dual-camera stereo vision system based on a Xilinx Spartan-6 FPGA + SDRAM, paired with an STM32F7 microcontroller.

2026 — Modern C++ Rewrite for SBCs

The library has been completely redesigned and rewritten from the ground up in modern C++17.

It preserves the original philosophy of extreme efficiency born on 8-bit microcontrollers nearly 20 years ago, now running efficiently on general-purpose CPUs with AVX2 optimizations where available.


🛠 Technical Specifications


Dependencies


Limitations & Trade-offs

⚠️ Best Practices for Optimal Results
The algorithm was originally proven on pristine, uncompressed RAW video streams. When using modern compressed sources (e.g., MJPEG/MP4 webcams on SBCs), compression artifacts and blurring can degrade contour accuracy.

Recommendation: For best results, apply a lightweight pre-processing step (e.g., hardware-accelerated thresholding, sharpening, or edge-enhancement) before passing the frame to vhliboptimal, or tune minColorVal and spccnt to be more tolerant of digital noise.


Before the startup procedure, image source parameters and settings are specified

Additionally:

Or:

Structure sizes

Configuration Examples

Parameter Example #1 Example #2 Example #3 Example #4
Maximum number of figures 128 128 128 128
Cell size 8 px 4 px 2 px 1 px
Resolution 800×600 800×600 800×600 800×600
Cells per frame 100×75 200×150 400×300 800×600
Bitmask (bits) 7500 30000 120000 480000
Bitmask (bytes) 938 3750 15000 60000
×2 bitmasks (Global + Local) 1876 7500 30000 120000
Memory (Worst case) 60000 bytes 240000 bytes 960000 bytes 3840000 bytes
Memory (Typical) 16000 bytes 64000 bytes 240000 bytes 960000 bytes
+128 figures × 16 2048 bytes 2048 bytes 2048 bytes 2048 bytes

Architecture & Key Components

The library operates completely abstracted from raw graphic decoders or UI frameworks (like OpenCV or stb_image). It processes data streams through an abstract coordinate grid:

Key Data Structures (src/vhliboptimalstructs.hpp)


Quick Start & Integration Example

Callbacks Architecture

The library is completely decoupled from image sources and result processing.

1. CallbackGetSrcPxls — Fetch source image pixels

/**
 * CallbackGetSrcPxls - Read a horizontal line of pixels from source image
 * 
 * @param userData   User context pointer (passed through from Setup)
 * @param dstptr     Destination buffer to fill
 * @param bytescnt   Number of bytes to read
 * @param srcid      Source image ID
 * @param srcx       Starting X coordinate
 * @param srcy       Starting Y coordinate
 */
typedef void (*CallbackGetSrcPxls)(void *userData, uint8_t *dstptr, 
                                   uint16_t bytescnt, uint16_t srcid, 
                                   uint16_t srcx, uint16_t srcy);

2. CallbackBorder — Process figure border (contour)

/**
 * CallbackBorder - Called during border tracing of a detected shape
 * 
 * @param userData   User context pointer
 * @param cmd        Command: cmdStart / cmdMove / cmdStop
 * @param dirh       Horizontal direction
 * @param dirv       Vertical direction
 * @param cellx      Cell X coordinate
 * @param celly      Cell Y coordinate
 * @param imgx       Image X coordinate (pixels)
 * @param imgy       Image Y coordinate (pixels)
 */
typedef void (*CallbackBorder)(void *userData, uint8_t cmd, uint8_t dirh, 
                               uint8_t dirv, uint16_t cellx, uint16_t celly, 
                               uint16_t imgx, uint16_t imgy);

3. CallbackContent — Process horizontal spans inside the figure

/**
 * CallbackContent - Called for each horizontal span inside the object
 * 
 * @param userData   User context pointer
 * @param cell1      Left / Top cell index
 * @param cell2      Right / Bottom cell index
 * @param dir        Direction 0: LR 1: UD
 */
typedef void (*CallbackContent)(void *userData, uint32_t cell1, uint32_t cell2, uint8_t dir);

Basic Usage Example

#include <iostream>
#include "vhliboptimal.hpp"

namespace vhliboptimal {

// Callback to fetch pixels from your framebuffer/camera
void MyGetPixels(void* userData, uint8_t* dstptr, uint16_t bytescnt,
                 uint16_t srcid, uint16_t srcx, uint16_t srcy) {
    // TODO: Fill dstptr with real image data
    // Example (dummy):
    // std::memset(dstptr, 0, bytescnt); // all black
}

// Callback for shape border tracing
void MyBorderCallback(void* userData, uint8_t cmd, uint8_t dirh, uint8_t dirv,
                      uint16_t cellx, uint16_t celly, uint16_t imgx, uint16_t imgy) {
    std::cout << "Border [cmd=" << (int)cmd 
              << ", dir=" << (int)dirh << "/" << (int)dirv 
              << "] cell(" << cellx << "," << celly 
              << ") px(" << imgx << "," << imgy << ")\n";
}

// Callback for internal content spans
void MyContentCallback(void* userData, uint32_t cell1, uint32_t cell2, uint8_t dir) {
    std::cout << "Content span: cells " << cell1 << " to " << cell2 << "\n";
}

} // namespace vhliboptimal

int main() {

    using namespace vhliboptimal;

    VHLibOptimal detector;

    // 1. Configuration
    stConfig cfg;

    cfg.imageWidth      = 800;
    cfg.imageHeight     = 600;

    cfg.cellsize        = 8;        // Grid cell size in pixels
    cfg.spccnt          = 2;        // Max consecutive empty cells (noise tolerance)
    cfg.minColorVal     = 128;      // Brightness threshold

    cfg.min_obj_width   = 32;
    cfg.min_obj_height  = 32;

    cfg.max_obj_width   = 256;
    cfg.max_obj_height  = 256;

    cfg.loglevel        = vhliboptimal::LOG_LEVEL_BASE;


    // 2. Setup with callbacks
    verr result = detector.Setup(cfg, 
                                 MyGetPixels, 
                                 MyBorderCallback, 
                                 MyContentCallback);

    if (result != vok) {
        std::cerr << "Setup failed!" << std::endl;
        return -1;
    }

    // 3. Run processing
    result = detector.Run(0);   // srcimgid = 0 (you can use multiple IDs)

    if (result == vok) {
        std::cout << "Scan completed successfully!\n";
        std::cout << "Objects found: " << detector.GetObjectsCount() << "\n";
        
        for (size_t i = 0; i < detector.GetObjectsCount(); ++i) {
            const VHOptimalFigure& fig = detector.GetObject(i);
            std::cout << "  Figure " << i 
                      << ": " << fig.SpansCount() << " spans, "
                      << "rect (" << fig.PosCells().x1 << "," 
                      << fig.PosCells().y1 << ") - ("
                      << fig.PosCells().x2 << "," 
                      << fig.PosCells().y2 << ")\n";
        }
    }

    return 0;
}

💡 A Fun Geek Note: 2016 FPGA vs 2026 C++ (The Branching Dilemma)

You might wonder: How does the modern C++ version compare to the 2016 FPGA implementation?
The answer lies in the fundamental difference between hardware pipelines and software execution when dealing with unpredictable branching during contour tracing.

🛑 The Dilemma: CPU Reality

Tracing irregular shapes creates chaotic control flow. Branch mispredictions (incurring a 15–25 cycle penalty on modern cores) and data-dependent bitwise operations on dynamic BitField indices create significant pipeline stalls. SIMD (AVX2/NEON) helps a lot during the initial grid scanning, but is nearly useless in the extraction and tracing phase.

⚙️ The 2016 FPGA Advantage

The 2016 Spartan-6 implementation used a hardware Finite State Machine (FSM) and dedicated Block RAM (BRAM), evaluating neighbor states instantly with minimal latency and perfect determinism. Even running at a modest ~166 MHz, it achieved outstanding efficiency in the core loop.

The FPGA version utilized massive hardware parallelism, processing bitfields instantly via dedicated BRAM. The original FPGA implementation demonstrated that the algorithm maps efficiently to hardware because of its regular grid-based data flow and compact BitField representation.

🔄 Architectural Shift: From Hardware Pipelines to Commodity Software

The modern C++ implementation preserves the same memory-efficient architecture while targeting commodity CPUs and SBCs. However, transitioning to modern C++ on general-purpose CPUs introduces critical microarchitectural differences (cache hierarchy, memory walls, and heavy dependency on branch prediction).

While pure FPS on a 2022 laptop or an ARM SBC might currently seem lower than the dedicated 2016 hardware pipeline, the trade-off shifts the value to extreme maintainability, rapid integration, and deployment on low-cost single-board computers (like Orange Pi/Raspberry Pi) without requiring expensive custom FPGA tooling.

🚀 The 2026 Reality

Thanks to aggressive grid downsampling, careful BitField design, and raw CPU clock speeds, the C++ version delivers practical real-time performance (depending on CPU, resolution, and scene complexity) on affordable SBCs.

It preserves the original philosophy of extreme efficiency born on 8-bit microcontrollers nearly 20 years ago, now running efficiently on general-purpose CPUs with AVX2 optimizations where available. While the 2016 FPGA version remains superior in raw tracing latency and absolute timing predictability, the 2026 rewrite brings that hard-earned efficiency to the modern software edge.


Library tests and benchmark project

Test and benchmark project: vhliboptimal_test


Legacy Artifact

A surviving header (.h) file of this original plain C implementation is preserved as a historical reference at electrolviv/optimal.


ChangeLog

0.7.5
0.7.4
0.7.3

© 2006 – 2026 V01G04A81 / Viktor Glebov