Who is this for?
C++ engineers and researchers building real-time computer vision and ML systems who want compile-time safety, composable pipelines, and ML-ready utilities — without leaving the OpenCV ecosystem.
- Status
- Getting Started
- API Comparison
- Features
- Quick Start
- Usage
- Augmentation
- Video File Capture
- Background Subtraction
- Motion Analysis
- Video Recording
- ONNX Runtime Inference
- Edge Detection & Enhancement
- Drawing Detections
- Object Tracking
- Lazy Views
- Feature Detection Pipeline
- Requirements
- Installation
- CMake Integration
- CMake Configuration (CLion)
- Running Tests
- Tested With
- Non-Goals & Limitations
- Motivation
- Benchmarks
- Contributing
v1.0.3 — Stable API release. All namespaces frozen; breaking changes only in future major versions. Highlights:
improc/improc.hppsingle-include umbrella, optional ONNX build (-DIMPROC_WITH_ONNX=OFF),DetectHaar, BRISK/KAZE feature ops,DnnSegmentor,HOGDetector.
| Namespace | Status | Notes |
|---|---|---|
improc::core |
✅ Stable | Photo/creative, quality metrics, perceptual hashing |
improc::calib |
✅ Stable | Camera calibration, stereo, pose, ArUco |
improc::io |
✅ Stable | |
improc::ml |
✅ Stable | DNN inference, HOG, segmentation, augmentation, tracking |
improc::threading |
✅ Stable | |
improc::visualization |
✅ Stable | Charts, plots, ML-specific visualisations |
improc::views |
✅ Stable | Lazy image pipeline adapters |
improc::onnx |
✅ Stable | ONNX Runtime 1.24.4; CPU + CoreML on Apple Silicon; optional (-DIMPROC_WITH_ONNX=ON by default) |
improc::cuda |
🔜 Planned | GPU-accelerated ops via OpenCV CUDA |
- C++23 compiler: GCC 14+ or Clang 18+
- CMake 3.30+
- Conan 2:
pip install "conan>=2,<3" - macOS: Xcode Command Line Tools (
xcode-select --install) - Linux:
sudo apt-get install build-essential cmake libgtk-3-dev
# 1. Detect your compiler profile (run once)
conan profile detect --force
# 2. Configure — Conan installs OpenCV, GTest automatically
cmake \
-DCMAKE_PROJECT_TOP_LEVEL_INCLUDES="conan_provider.cmake" \
-DCONAN_COMMAND=$(which conan) \
-DCMAKE_BUILD_TYPE=Release \
-B build .
# 3. Build
cmake --build build --parallel
# 4. Run tests
./build/improc_tests#include "improc/improc.hpp" // single include — pulls in all namespaces
using namespace improc::core;
using namespace improc::io;
int main() {
auto img = imread<BGR>("photo.png");
if (!img) { return 1; }
Image<BGR> result = *img
| Resize{}.width(224).height(224)
| GaussianBlur{}.kernel_size(3)
| Brightness{}.delta(20.0);
imwrite("output.png", result);
}| Task | Raw OpenCV | improc++ |
|---|---|---|
| Resize to 224×224 | cv::resize(src, dst, cv::Size(224,224)) |
img | Resize{}.width(224).height(224) |
| Convert to float | src.convertTo(dst, CV_32FC3, 1.0/255) |
img | ToFloat32C3{} | NormalizeTo{0.f, 1.f} |
| Gaussian blur | cv::GaussianBlur(src, dst, cv::Size(3,3), 0) |
img | GaussianBlur{}.kernel_size(3) |
| Edge detection | cv::Canny(gray, dst, 100, 200) |
img | CannyEdge{}.threshold1(100).threshold2(200) |
| Warp perspective | cv::warpPerspective(src, dst, H, cv::Size(w,h)) |
img | WarpPerspective{}.homography(H).width(w).height(h) |
Format mismatches (e.g. passing Image<Float32> where Image<BGR> is expected) are compiler errors, not silent runtime bugs.
- Not a Python library yet. Python bindings are on the long-term roadmap — use OpenCV's Python API in the meantime.
- Not a general-purpose image editor. No GUI, no layers, no undo — this is a processing pipeline toolkit.
- Not a training framework. No autograd, no loss functions — use PyTorch/TensorFlow for training; improc++ handles inference and preprocessing.
- No CUDA yet. All ops run on CPU (ONNX Runtime uses CoreML EP on Apple Silicon). CUDA acceleration (
improc::cuda) is planned. - OpenCV dependency is visible.
cv::Mat,cv::Point2fetc. appear in the public API — improc++ wraps, not hides, OpenCV. - C++23 required. No support for older standards.
OpenCV is powerful but its raw API is stringly-typed, mutation-heavy, and easy to misuse — passing a CV_32FC1 mat where a CV_8UC3 is expected silently produces garbage instead of a compiler error. improc++ wraps OpenCV with a thin, low-overhead abstraction that makes format mismatches impossible at compile time, composes processing steps into readable pipelines, and provides ML-ready utilities (augmentation, dataset loading, DNN inference) without reinventing the wheel. Benchmarks on Apple M4 Pro show wrapper overhead of ≤0.9% for most ops; CLAHE and GammaCorrection carry additional per-call cost from object/LUT construction in the current implementation. The goal is code that reads like a description of what it does, not how OpenCV internals work.
- Type-safe image wrapper —
Image<BGR>,Image<Gray>,Image<Float32>etc. catch format mismatches at compile time - Composable pipeline —
image | Resize{}.width(224) | ToFloat32C3{}syntax for readable processing chains - Geometric operations —
Resize(aspect-ratio aware),Crop,CenterCrop,LetterBox,Flip,Rotate,Pad,PadToSquare,WarpAffine,WarpPerspective,find_homography,Remap(general pixel remapping) - Filter & morphology —
GaussianBlur,MedianBlur,BoxFilter,Convolve(custom kernel),Dilate,Erode,MorphOpen,MorphClose,MorphGradient,TopHat,BlackHat;Threshold(incl. Otsu & adaptive),CLAHE,GammaCorrection,BilateralFilter,HistogramEqualization,NLMeansDenoising,UnsharpMask - Edge & corner detection —
SobelEdge,CannyEdge,LaplacianEdge,HarrisCorner;SobelGradient/ScharrGradient(raw CV_16S derivative pairs) +ConvertScaleAbs;GoodFeaturesToTrack(Shi-Tomasi / Harris);HoughLinesP,HoughCircles; all acceptImage<Gray>orImage<BGR>(auto-converted) - Colour spaces —
ToHSV,ToLAB,ToYCrCb,ToGray,ToBGR(from any of the above); format tagsHSV,LAB,YCrCbenforce colour-space safety at compile time - Pyramid ops —
PyrDown(halves),PyrUp(doubles); work on anyImage<Format> - Drawing / annotation —
DrawText,DrawLine,DrawCircle,DrawRectangle; all operate on a clone (non-mutating) - Contour analysis —
FindContours→ContourSet(witharea(),perimeter(),bounding_rect());DrawContours(fill or stroke, single or all);ConvexHull,ApproxPolyDP(Douglas-Peucker),MinAreaRect(cv::RotatedRect),BoundingRect - Connected components —
ConnectedComponents→ComponentMap(labels, stats, centroids, per-component masks);DistanceTransform→Image<Float32> - Feature detection pipeline —
DetectORB/DetectSIFT/DetectAKAZE→KeypointSet;DescribeORB/DescribeSIFT/DescribeAKAZE→DescriptorSet;MatchBF/MatchFlann→MatchSet;DrawKeypoints/DrawMatchesfor visualisation - Detector ops —
DetectFAST(FAST corners),DetectBlob(SimpleBlobDetector),DetectMSER(extremal regions),DetectLines(LSD line segments),DetectQR(QR detect + decode),DetectBarcode(barcode detect + decode, no model file);DetectFaceYN(YuNet face detector, model-gated),RecognizeFace(SFace embed + cosine similarity match, model-gated) - Photo / creative ops —
EdgePreservingFilter,DetailEnhance,Stylize,PencilSketch(returns{gray, color}pair),SeamlessClone(Poisson blending),NLMeansDenoisingMulti(temporal denoising),MergeHDR(Mertens/Debevec →Image<Float32C3>),ToneMap(Linear/Drago/Reinhard/Mantiuk HDR→LDR); panoramaStitch(returnsStitchResult{ok, panorama}) - Quality metrics —
PSNR(peak signal-to-noise ratio, ∞ for identical),SSIM(structural similarity, 1.0 for identical),GMSD(gradient magnitude similarity deviation, 0.0 for identical),MSE(mean squared error); BGR and Gray overloads; standard OpenCV only, no contrib - Perceptual hashing —
AverageHash,PHash(DCT-based),MarrHildrethHash(LoG sign bits),RadialVarianceHash,ColorMomentHash,BlockMeanHash; each exposes::distance(h1, h2)for the canonical similarity metric; standard OpenCV only, no contrib - Camera calibration (
improc::calib) —FindChessboardCorners/FindChessboardCornersSB/RefineCorners;CalibrateCamera+make_chessboard_points();StereoCalibrate;Undistort(pipeline op) +UndistortMap(precomputed remap tables) - Pose estimation (
improc::calib) —SolvePnP,SolvePnPRansac(RANSAC outlier rejection),ProjectPoints;StereoRectify;ReprojectTo3D;StereoBM/StereoSGBMdisparity maps - Epipolar geometry (
improc::calib) —FindFundamentalMat,FindEssentialMat,RecoverPose,TriangulatePoints - ArUco / ChArUco (
improc::calib) —ArucoDict,DetectAruco,DrawAruco,GenerateAruco,ArucoPose,CharucoBoard - Pixel ops —
InRange(binary mask),Invert,Brightness,Contrast,WeightedBlend,AlphaBlend,ApplyMask; element-wiseAdd,Subtract,Multiply,Divide,AbsDiff; bitwiseBitwiseAnd,BitwiseOr,BitwiseNot;LUT(256-entry lookup table) - Channel ops —
SplitChannels(BGR/BGRA → vector of Gray),MergeChannels(vector of Gray → BGR/BGRA) - Normalization —
Normalize,NormalizeTo,Standardizefor ML preprocessing - Format conversions — explicit, compiler-enforced free functions (
convert<Gray>(bgr_image));Flowformat tag (CV_32FC2) for dense optical flow fields - Motion analysis —
SparseLKFlow(Lucas-Kanade, tracksstd::vector<cv::Point2f>),DenseFarnebackFlow(Farneback dense flow →Image<Flow>),DenseDISFlow(DIS fast/medium/ultrafast →Image<Flow>),CamShift(adaptive MeanShift tracker),MeanShift(kernel-based shift),PhaseCorrelate(sub-pixel frequency-domain shift estimation) - Image analysis —
IntegralImage(summed-area table),MinMaxLoc,MeanStdDev(per-channel),CountNonZero,Reduce(Sum/Avg/Max/Min to row or column);CalcHist/CompareHist;Moments;MatchTemplate - Segmentation & region ops —
Inpaint(TELEA/NS),Watershed(marker-based),GrabCut(foreground/background),FloodFill(BGR and Gray) - Augmentation — stochastic training augmentations constrained by C++20 concepts:
RandomFlip,RandomRotate,RandomCrop,RandomResize,RandomZoom,RandomShear,RandomPerspective,RandomBrightness,RandomContrast,ColorJitter,RandomGrayscale,RandomSolarize,RandomPosterize,RandomEqualize,RandomBlur,RandomSharpness,RandomGaussianNoise,RandomSaltAndPepper,RandomErasing,GridDropout,Compose,RandomApply,OneOf; bbox-aware overloads viaBBox,AnnotatedImage<F>,BBoxCompose<F>(all 7 geometric ops accept annotated images with automatic clip-and-drop filtering) - Multi-object tracking —
IouTracker(greedy IoU),SortTracker(constant-velocity Kalman + Hungarian, SORT algorithm),ByteTracker(two-stage BYTE algorithm: Stage 1 Hungarian on high-confidence dets, Stage 2 greedy IoU on low-confidence dets); all satisfyTrackerType<T>and are drop-in replaceable;TrackingEvalaccumulates MOTA, MOTP, IDF1, Precision, Recall, FP, FN, IDSW across frames - Dataset loading — load image datasets from class-labeled directories with train/val/test splitting
- DNN inference —
DnnClassifier,DnnDetector(YOLO & SSD),DnnForwardbacked by OpenCV DNN; pipeline-composable - ONNX Runtime inference —
OnnxClassifier,OnnxDetector(YOLOv5 & YOLOv8 & SSD),OnnxSession(raw tensor I/O); CPU + CoreML EP on Apple Silicon; train in Python, export to ONNX, run here - Camera capture — asynchronous threaded frame capture via
WebcamCapture; explicitstart()/stop()lifecycle;getFrame()returnsstd::expected<CameraFrame, Error>for safe error handling - Video file capture —
VideoFileCapturereads video files frame-by-frame as aCameraSourceType; satisfiesCameraSourceType<VideoFileCapture>;getFrame()returnsError::EndOfFileat EOF; fully compatible withAnyCameraSourceandFramePipeline - Background subtraction —
BackgroundSubtractMOG2(Gaussian Mixture Model) andBackgroundSubtractKNN(K-Nearest Neighbours); stateful — pass as lvalue tooperator|to accumulate model across frames; returnsImage<Gray>foreground mask - Video recording — synchronous RAII
VideoWriterwith auto codec detection and pipeline support (img | Show{"preview"} | writer) - Haar Cascade loader — CRTP-based model loader for OpenCV cascade classifiers
- Threading —
ThreadPoolandFramePipeline<Result>for real-time frame processing - Visualization —
Histogram,LinePlot,Scatterchart functors, aShowpassthrough display op,DrawBoundingBoxesfor annotating detections,DrawTracksfor annotating tracker output, andMontagefor image grid composition — all composable withoperator|; plus ML-specific charts viaml_charts.hpp:ConfusionMatrixPlot(heatmap),PRCurvePlot(precision-recall curves),ROCCurvePlot(ROC with AUC),ClassBarChart(P/R/F1 or AP bars),IoUHistogram - Lazy image views —
improc::viewslazy pipeline adapters:transform,filter,take,drop,batch(N),enumerate,zip; compose withfrom_dir(),VideoView, andstd::vector<Image<F>>sources viaoperator|; zero work until materialised withviews::to<T>()
#include "improc/core/pipeline.hpp"
#include "improc/io/webcam_capture.hpp"
#include "improc/io/video_writer.hpp"
#include "improc/visualization/visualization.hpp"
using namespace improc::core;
using namespace improc::io;
using namespace improc::visualization;
// 1. Load an image and run a preprocessing pipeline
cv::Mat raw = cv::imread("photo.jpg");
Image<BGR> result = Image<BGR>(raw)
| Resize{}.width(224).height(224)
| CLAHE{}.clip_limit(2.0)
| GaussianBlur{}.kernel_size(3);
// 2. Convert for model inference
Image<Float32C3> tensor = result | ToFloat32C3{} | NormalizeTo{0.0f, 1.0f};
// 3. Display a histogram of the processed image
result | Histogram{} | Show{"Histogram"};
// 4. Record from camera with live preview
WebcamCapture cam(0);
cam.start();
VideoWriter writer{"output.mp4"};
writer.fps(30);
while (true) {
auto frame = cam.getFrame();
if (!frame || !frame->rgb) continue;
*frame->rgb | Show{"Live"}.wait_ms(1) | writer; // display + record in one pipeline
if (cv::waitKey(1) == 27) break; // ESC to stop
}
cam.stop();All ops are functors that compose via operator|. For a full reference of every namespace, op, and error type see NAMESPACES.md.
// Single include — pulls in all namespaces
#include "improc/improc.hpp"
// Or include only what you need:
#include "improc/core/pipeline.hpp" // all core ops
#include "improc/io/io.hpp" // webcam, video, image I/O
#include "improc/ml/augmentation.hpp" // augmentation ops
#include "improc/ml/tracking/tracking.hpp" // IouTracker, SortTracker, ByteTracker
#include "improc/onnx/onnx.hpp" // OnnxSession, OnnxClassifier, OnnxDetector (requires IMPROC_WITH_ONNX)
#include "improc/visualization/visualization.hpp"
#include "improc/views/views.hpp"Stochastic augmentations for training pipelines. Each op takes (Image<Format>, std::mt19937&) directly or via .bind(rng) for operator| use. Format constraints are enforced at compile time via C++20 concepts.
#include "improc/ml/augmentation.hpp"
using namespace improc::ml;
std::mt19937 rng(42);
// Single op — direct call
Image<BGR> flipped = RandomFlip{}.p(0.5f)(img, rng);
// Pipeline via .bind()
Image<BGR> result = img
| RandomFlip{}.p(0.5f).bind(rng)
| RandomBrightness{}.range(0.8f, 1.2f).bind(rng);
// Full training augmentor
auto augmentor = Compose<BGR>{}
.add(RandomFlip{}.p(0.5f))
.add(RandomRotate{}.range(-10.0f, 10.0f))
.add(RandomApply<BGR>{ColorJitter{}, 0.5f})
.add(OneOf<BGR>{}
.add(RandomGaussianNoise{}.std_dev(5.0f, 15.0f))
.add(RandomSaltAndPepper{}.p(0.02f)));
Image<BGR> augmented = augmentor(img, rng);
// or: Image<BGR> augmented = img | augmentor.bind(rng);See examples/ml/demo_augmentation.cpp for a full walkthrough.
All 7 geometric ops also accept AnnotatedImage<Format> — ground-truth boxes are transformed alongside the image. Boxes that fall too far outside the frame after the transform are automatically dropped.
using namespace improc::ml;
BBox cat{cv::Rect2f(50.f, 50.f, 200.f, 150.f), 0, "cat"};
AnnotatedImage<BGR> ann{img, {cat}};
// Single op — direct call
AnnotatedImage<BGR> result = RandomFlip{}.p(0.5f)(ann, rng);
// BBoxCompose pipeline
BBoxCompose<BGR> pipeline;
pipeline
.add([](auto a, auto& r){ return RandomFlip{}.p(0.5f)(std::move(a), r); })
.add([](auto a, auto& r){ return RandomRotate{}.range(-15.f, 15.f)(std::move(a), r); })
.add([](auto a, auto& r){ return RandomCrop{}.width(224).height(224)(std::move(a), r); });
auto out = pipeline(std::move(ann), rng);
// or: auto out = ann | pipeline.bind(rng);
std::cout << out.boxes.size() << " boxes after augmentation\n";
// Tune the drop threshold per-op (default 0.1 — drop if < 10% visible)
RandomCrop{}.min_area_ratio(0.3f).width(200).height(200)(ann, rng);Read video files with the same API as live cameras:
#include "improc/io/io.hpp"
using namespace improc::io;
VideoFileCapture cap{"clip.mp4"};
cap.start();
while (true) {
auto frame = cap.getFrame();
if (!frame) break; // EndOfFile
// (*frame).rgb is std::optional<Image<BGR>>; dereference before use
}
cap.stop();Works with AnyCameraSource and FramePipeline — swap a live camera for a file with no code changes:
auto src = AnyCameraSource::make<VideoFileCapture>("clip.mp4");Stateful foreground extraction. Pass as lvalue to preserve the learned model across frames:
#include "improc/core/pipeline.hpp"
using namespace improc::core;
BackgroundSubtractMOG2 sub;
sub.history(300).threshold(16.0).detect_shadows(false);
// In frame loop (frame is Image<BGR> from camera/VideoFileCapture):
Image<BGR> frame = /* your camera frame */;
Image<Gray> fg_mask = frame | sub; // sub is an lvalue — model accumulatesOptical flow, shift tracking, and frequency-domain motion estimation:
#include "improc/core/pipeline.hpp"
using namespace improc::core;
Image<Gray> prev = ..., next = ...;
std::vector<cv::Point2f> pts = { {100, 100}, {200, 150} };
// Sparse Lucas-Kanade — track specific points
SparseLKFlowResult lk = SparseLKFlow{}.win_size(21).max_level(3)(prev, next, pts);
for (size_t i = 0; i < pts.size(); ++i)
if (lk.status[i])
// lk.points[i] is the new position
// Dense DIS flow — fast, returns Image<Flow> (CV_32FC2)
Image<Flow> flow = prev | DenseDISFlow{}.preset(DenseDISFlow::Preset::UltraFast);
// Dense Farneback flow — higher quality
Image<Flow> flow2 = prev | DenseFarnebackFlow{}.iterations(3);
// Phase correlate — sub-pixel global shift estimation
Image<Float32> pf = prev | ToFloat32{};
Image<Float32> nf = next | ToFloat32{};
PhaseCorrelateResult shift = PhaseCorrelate{}(pf, nf);
// shift.shift is cv::Point2d; shift.response is confidence [0,1]CamShift / MeanShift for region tracking (takes back-projection from CalcHist):
cv::Rect window{x, y, w, h};
CamShiftResult r = CamShift{}(back_proj, window); // window updated in-place
// r.object is cv::RotatedRect; r.iterations is convergence count#include "improc/io/video_writer.hpp"
using improc::io::VideoWriter;
// Basic recording — codec and size auto-detected
VideoWriter writer{"output.mp4"};
writer.fps(30);
writer(frame); // write a frame
writer.close(); // or let the destructor finalise
// Pipeline form — display and record simultaneously
img | Show{"preview"}.wait_ms(1) | writer;
// Manual configuration
VideoWriter w{"output.avi"};
w.fps(25).size(640, 480).codec("MJPG");Supported auto-codecs: .mp4/.mov → mp4v, .avi → MJPG, .mkv → XVID.
Requires
-DIMPROC_WITH_ONNX=ON(the default). Pass-DIMPROC_WITH_ONNX=OFFto build without ONNX Runtime.
Train models in Python and run inference here. OnnxClassifier and OnnxDetector share the same fluent API as their Dnn* counterparts. OnnxSession gives you raw tensor access for custom model architectures.
#include "improc/onnx/onnx.hpp"
using namespace improc::onnx;
// Classification — export from PyTorch with torch.onnx.export()
OnnxClassifier cls{"mobilenet.onnx"};
cls.input_size(224, 224)
.mean(0.485f, 0.456f, 0.406f)
.scale(1.0f / 255.0f)
.labels(class_names)
.top_k(5);
auto results = cls(img); // std::expected<std::vector<ClassResult>, Error>
if (results)
for (const auto& r : *results)
std::cout << r.label << ": " << r.score << "\n";
// Detection — YOLOv8 ONNX export: model.export(format="onnx")
OnnxDetector det{"yolov8n.onnx"};
det.input_size(640, 640).confidence_threshold(0.5f).labels(class_names);
auto boxes = det(frame); // std::expected<std::vector<Detection>, Error>
if (boxes)
frame | DrawBoundingBoxes{*boxes} | Show{"Detections"}.wait_ms(1);
// Raw session — custom architectures, multiple outputs
OnnxSession session;
session.load("encoder.onnx");
auto outputs = session.run({{session.input_names()[0], {1,3,224,224}, data}});On Apple Silicon, the CoreML execution provider is registered automatically — compatible ops run on the Neural Engine with transparent CPU fallback.
#include "improc/core/pipeline.hpp"
using namespace improc::core;
// Sobel edge magnitude (accepts Gray or BGR)
Image<Gray> edges = Image<Gray>(gray_mat) | SobelEdge{}.ksize(3);
Image<Gray> edges2 = Image<BGR>(bgr_mat) | SobelEdge{}; // auto-converts
// Canny (two-threshold hysteresis)
Image<Gray> canny = img | CannyEdge{}.threshold1(50).threshold2(150);
// Gamma correction (any format)
Image<BGR> brighter = img | GammaCorrection{}.gamma(0.5f); // brighten
Image<BGR> darker = img | GammaCorrection{}.gamma(2.0f); // darken
// Bilateral filter (edge-preserving smoothing)
Image<BGR> smooth = img | BilateralFilter{}.diameter(9).sigma_color(75).sigma_space(75);#include "improc/visualization/draw.hpp"
using namespace improc::visualization;
// After running DnnDetector
std::vector<improc::ml::Detection> detections = detector(frame);
// Annotate a copy of the frame
Image<BGR> annotated = frame | DrawBoundingBoxes{detections}.thickness(2);
// Suppress labels or confidence scores
Image<BGR> boxes_only = frame | DrawBoundingBoxes{detections}
.show_label(false).show_confidence(false);Three interchangeable trackers, all satisfying TrackerType<T>: IouTracker (greedy), SortTracker (Kalman + Hungarian, SORT), and ByteTracker (two-stage BYTE). Pair with TrackingEval for offline metric computation.
#include "improc/ml/tracking/tracking.hpp"
using namespace improc::ml;
// --- SortTracker — Kalman-filter SORT algorithm ---
SortTracker tracker;
tracker.max_age(3).min_hits(3).iou_threshold(0.3f);
for (auto& frame : video) {
std::vector<Detection> dets = detector(frame);
std::vector<Track> tracks = tracker.update(dets); // confirmed tracks only
// DrawTracks draws bbox + "ID:N" label — pipeline-compatible
Image<BGR> annotated = frame | DrawTracks{tracks}.thickness(2);
}
// --- ByteTracker — two-stage matching recovers occluded tracks ---
ByteTracker bt;
bt.max_age(3).min_hits(3)
.high_conf_threshold(0.6f).low_conf_threshold(0.1f);
// --- Evaluation: MOTA / MOTP / IDF1 / Precision / Recall ---
TrackingEval eval;
eval.iou_threshold(0.5f);
for (int f = 0; f < n_frames; ++f)
eval.update(predicted_tracks[f], ground_truth[f]);
TrackingMetrics m = eval.compute();
std::cout << "MOTA=" << m.MOTA << " MOTP=" << m.MOTP
<< " IDF1=" << m.IDF1
<< " P=" << m.Precision << " R=" << m.Recall
<< " IDSW=" << m.IDSW << "\n";All trackers are drop-in replaceable — swap SortTracker for ByteTracker with no other changes. DrawTracks is in improc/visualization/draw_tracks.hpp (included via visualization.hpp). See NAMESPACES.md for the full setter reference.
Lazy pipeline adapters over image collections. Nothing executes until you materialise with views::to<T>() or a range-for loop.
#include "improc/views/views.hpp"
namespace views = improc::views;
using namespace improc::core;
std::vector<Image<BGR>> images = ...;
// transform + filter + take — materialise into a new vector
auto result = images
| views::filter([](const Image<BGR>& img) { return img.cols() >= 128; })
| views::transform(Resize{}.width(64).height(64))
| views::take(10)
| views::to<std::vector<Image<BGR>>>();
// lazy iteration over a directory — only loads images on demand
for (const auto& img : views::from_dir("dataset/train", {".png", ".jpg"})) {
// process img ...
}
// batch(N) — iterate over fixed-size chunks
for (const auto& chunk : views::from_dir("frames/", {".png"}) | views::batch(8)) {
// chunk is std::vector<Image<BGR>> of up to 8 images
}
// enumerate — zero-based index alongside each element
for (const auto& [idx, img] : images | views::take(5) | views::enumerate) {
std::cout << idx << ": " << img.cols() << "x" << img.rows() << "\n";
}
// zip — pair two sources element-wise (stops at the shorter)
for (const auto& [img, mask] : views::zip(images, masks)) {
// img and mask aligned by position
}
// compose freely — lazy VideoView source, then batch + enumerate
VideoReader reader{"video.mp4"};
for (const auto& [idx, chunk] : views::VideoView{reader} | views::batch(4) | views::enumerate) {
std::cout << "Batch " << idx << ": " << chunk.size() << " frames\n";
}See examples/views/ for the full demo suite (M1–M4) and NAMESPACES.md for the complete symbol reference.
#include "improc/core/pipeline.hpp"
using namespace improc::core;
Image<BGR> img1 = ..., img2 = ...;
Image<Gray> gray1 = img1 | ToGray{}, gray2 = img2 | ToGray{};
// Detect → Describe → Match
KeypointSet kps1 = gray1 | DetectORB{}.max_features(500);
DescriptorSet desc1 = gray1 | DescribeORB{kps1};
KeypointSet kps2 = gray2 | DetectORB{}.max_features(500);
DescriptorSet desc2 = gray2 | DescribeORB{kps2};
MatchSet ms = MatchBF{desc1, desc2}.cross_check(true)();
// Visualise
Image<BGR> kp_vis = img1 | DrawKeypoints{kps1};
Image<BGR> match_vis = DrawMatches{img1, kps1, img2, kps2, ms}();
// FLANN + Lowe ratio test (SIFT/float descriptors)
KeypointSet sift_kps = gray1 | DetectSIFT{};
DescriptorSet sift_desc = gray1 | DescribeSIFT{sift_kps};
MatchSet sift_ms = MatchFlann{sift_desc, sift_desc}.ratio_threshold(0.7f)();- C++23 or later
- OpenCV 4.10+
- ONNX Runtime 1.24.4 — optional, default ON (
-DIMPROC_WITH_ONNX=OFFto skip) - GoogleTest 1.16+
- Conan 2.0 for dependency management
-
Clone the repository:
git clone https://github.com/michalmaj/improc.git
-
Configure with CMake (Conan auto-invoked via
conan_provider.cmake):cmake -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES="conan_provider.cmake" \ -DCONAN_COMMAND=<path_to_conan> \ -B build . cmake --build build
After installing with cmake --install build, downstream projects can use:
find_package(improc REQUIRED)
target_link_libraries(my_app PRIVATE improc::improc)This pulls in OpenCV as a transitive dependency. ONNX Runtime is linked automatically when IMPROC_WITH_ONNX=ON (the default).
Set the following CMake options:
-DCMAKE_PROJECT_TOP_LEVEL_INCLUDES="conan_provider.cmake"
-DCONAN_COMMAND=<path_to_conan_executable>
Thread Sanitizer:
-DCMAKE_CXX_FLAGS="-fsanitize=thread -g -O1 -fno-omit-frame-pointer"
Address + Undefined Behavior Sanitizer:
-DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -g -O1 -fno-omit-frame-pointer"
-DCMAKE_BUILD_TYPE=Debug
# All tests
cmake --build cmake-build-debug --target improc_tests
./cmake-build-debug/improc_tests
# Single suite
./cmake-build-debug/improc_tests --gtest_filter="CLAHETest.*"- Compilers: GCC 14.2, Clang 19.1.7, Apple-Clang 16 (Xcode 16)
- OpenCV: 4.10.0
- GoogleTest: 1.16.0
Single-thread performance on Apple M4 Pro. Highlights:
| Operation | Time | Notes |
|---|---|---|
| GaussianBlur 480×640 | 82 µs | |
| DenseDISFlow UltraFast 480×640 | 0.9 ms | 3× faster than Farneback at lower quality |
| DenseFarnebackFlow 480×640 | 17.1 ms | Full dense flow field |
| SparseLKFlow 480×640 | 0.5 ms | per tracked point batch |
| Add / Subtract 480×640 | ~18 µs | wrapper overhead ≈0 vs raw cv::add |
| SobelGradient 480×640 | 99.6 µs | returns CV_16S dx+dy pair |
| DetectFAST 480×640 | 2.5 ms | |
| Undistort 480×640 | 1.9 ms | wrapper overhead ≈0 ns vs raw |
| StereoBM 480×640 | 3.8 ms | CV_16S disparity map |
| IouTracker @ 100 dets | 17.5 µs | |
Lazy take(16/256) |
16× faster than eager |
→ Full benchmark tables and engineering notes
Contributions are welcome. Fork the repository, create a feature branch (git checkout -b feature/my-op), add your changes with tests, and open a pull request against main. Please keep new ops consistent with the existing patterns: a fluent-setter functor in include/improc/, a matching .cpp in src/, tests in tests/, and the new type registered in the relevant umbrella header (pipeline.hpp for core ops, augmentation.hpp for augmentation ops). All pull requests must pass the full test suite before merging.