Camera SDK · C ABI · Edge inference

Computer vision that runs on the camera, and licensing that runs with it

XCAICX is an on-device CV SDK for AI camera manufacturers and the factories that deploy them. Detection, defect inspection, PPE compliance and plate localisation execute on the unit with no cloud round trip — and per-unit licensing and metered API billing are part of the runtime rather than something bolted on afterwards.

The demo below is not a video. It is the SDK's reference backend ported to JavaScript — same algorithms, same constants — running on your machine.

 camera · first boot
// firmware links one stable C ABI
xcaicx_config cfg;
xcaicx_config_init(&cfg);
cfg.license_path = "/etc/xcaicx/batch.token";
cfg.state_dir    = "/var/lib/xcaicx";
cfg.modules      = XCAICX_MODULE_DETECT | XCAICX_MODULE_PPE;

// activation happens once, on this unit
activated as : LIC-D3ACF708DB6048AB
bound device : b2cd209a6826bd8b33c0fdaad6304106
batch now    : 1/5000 activated

// then it runs, network or no network
event: LINE_CROSS      zone=doorway track=1
event: PPE_VIOLATION   no_helmet score=0.71
metered inferences: 40  (2 modules x 20 frames)

Live · runs on your machine

The reference backend, running in your browser

Every box, track and event below is produced by a direct port of backend_reference.cpp: the same Sobel/Otsu/connected-components detection, the same MAD-thresholded tile statistics for defects, the same HSV band ratios for PPE, the same plate geometry for ANPR, the same greedy-IoU tracker and even-odd zone test. Point it at a synthetic scene, or at your own camera.

xcaicx_stream_process()

Modules

A module the selected SKU does not entitle is struck through and cannot be switched on — the runtime refuses it, exactly as xcaicx_engine_create does.

0Analysis fps
0Pipeline ms
0Live tracks
0Metered calls

Controls

Event stream

No events yet.

This is the reference backend, and it is not production accuracy. It is contrast, edge and colour heuristics with no learned model behind it. It exists so an integrator can validate frame plumbing, licensing, events and metering before a model ever reaches them, and so CI is deterministic. On a real factory floor it will miss things and invent things. Production deployments build with XCAICX_WITH_ORT=ON and ship a model bundle; the engine logs the distinction at INFO on every start so nobody discovers it in the field.

Nothing leaves your machine. Frames from the synthetic scenes and from your camera are analysed in the page and never uploaded — which is the SDK's entire argument, so the website holds to it too.

Capability modules

Four modules, mapped one-to-one onto license entitlements

Each module is a bit in the license token. The engine refuses to start one whose bit is false, so what a customer bought and what their cameras can do cannot drift apart.

XCAICX_MODULE_DETECT

Detection & tracking

People, vehicles and objects, with a greedy-IoU tracker that coasts across skipped frames. Zones, directed tripwires and dwell timers turn tracks into events your VMS can act on.

XCAICX_MODULE_DEFECT

Defect inspection

Tile statistics against the frame's own robust median and MAD, so a real defect cannot inflate the statistics used to find it. Deliberately conservative: a false reject stops a production line.

XCAICX_MODULE_PPE

PPE compliance

Helmet and hi-vis checks on the head and torso bands of each detected person. It reports the absence, because the actionable event on a factory floor is the violation, not the compliance.

XCAICX_MODULE_ANPR

Plate localisation & OCR

Plates are dense horizontal-edge rectangles at a characteristic aspect ratio. Localisation runs on any build; reading the characters needs the text model in the production bundle.

The commercial loop

From a batch order to an invoice, without a spreadsheet in the middle

The same six steps run whether you ship five cameras or five hundred thousand. Every one of them is exercised by the test suite.

Sell a batch

An OEM buys N units. They receive one batch key — unbound, activation required — and nothing else. It is a licence to activate, not a licence to run.

$ xcaicxctl batch --customer CUST-CAMCO --sku oem-industrial --units 5000
batch      : B-2026-1C4417D5  (5000 units)
batch key  : XCAICX1.eyJsaWMiOiJMSUMtQkFUQ0gt…

Flash every camera with the same key

One image, one key, the whole production line. No per-unit provisioning step and no serial-number spreadsheet to keep in sync.

Each unit activates once

On first boot the SDK exchanges the batch key for a licence bound to that camera's hardware fingerprint, and the server decrements the batch counter. That decrement is the billing event. Re-activating the same device returns the existing token and consumes nothing — a reflash or an SD-card swap must never be a billing event.

POST /v1/activate
{ "batch_token": "XCAICX1.…", "device_id": "b2cd209a…" }

200 { "license_id": "LIC-D3ACF708DB6048AB",
      "reactivated": false, "batch_units_remaining": 4999 }

Frames go in, events come out

Feed whatever your ISP already produces — NV12 and I420 are first-class, so you are not paying for an RGB conversion on the boards that can least afford it. Tracking, zones and tripwires run on top.

Usage spools, then ships

One inference per module per frame is counted on-device, checkpointed to disk and resumed across reboots. The spool is cleared only after the server acknowledges, so an outage delays revenue rather than losing it. Replays are deduped server-side.

The period rates itself

Activated units, metered inference over the included allowance, and annual maintenance on perpetual SKUs, rated into an invoice per customer.

$ xcaicxctl invoice CUST-CAMCO --days 30
XCAICX OEM Industrial - per unit          1,204 unit
XCAICX OEM Industrial - annual maintenance  892 unit-year
TOTAL                                      USD

Licensing · live

Poke at the enforcement yourself

A licence is a signed, self-contained string: XCAICX1.<base64url(payload)>.<base64url(ed25519_sig)>. The lab below generates a throwaway Ed25519 keypair in your browser, mints a real token against it, and verifies it with the same rules the C++ verifier applies on the camera. Try to break it.

token lab · real Ed25519 · in-page keypair

Token

Verifier verdict

Decoded payload


            

In-page public key: — generated here, discarded when you close the tab. The production root key never leaves the licensing server; its public half is compiled into the SDK, so a camera verifies with no network and nothing on the device can mint a licence.

activation · seat accounting
Units activated 0 / 5

5 units remaining on this batch.

Server responses

No activations yet.

Offline licensing is deterrence, not DRM. An attacker with root on the camera and a disassembler can patch out the check — that is true of every licensing scheme running on hardware the attacker controls. What this design buys is that casual copying is dead, entitlements cannot be widened without forging Ed25519, seat accounting is server-side and cannot be inflated on-device, and weak fingerprint sources are recorded per activation so a cluster of trivially-clonable units on one account is visible to you.

What the enforcement stops, each row covered by the 45 checks in test_license.cpp.
AttackResult
Flip a byte in the payloadBadSignature
Re-encode with anpr: true, keep the signatureBadSignature
Copy a licence to another cameraWrongDevice
Wind the clock back a yearClockRollback
Use a token past its termExpired, after the grace window
Use a revoked licenceRevoked
Forge a batch key with your own key403 at activation
Present a validly-signed key we never sold403 — no seat behind it
Replay a usage record to avoid billingDeduped server-side
Reboot to reset the metered counterCounters resume from disk

Integration

One header is the entire supported surface

Deliberately a C ABI, not a C++ one. Camera vendors build with whatever toolchain their SoC BSP ships — GCC 7 on a Rockchip Yocto image, NDK clang on Android, MSVC on a Windows NVR — and a C++ ABI breaks across every one of them. Structs carry a leading struct_size, so a binary compiled today keeps working against a future SDK.

 C — the OEM path
#include "xcaicx/xcaicx.h"

xcaicx_config cfg;
xcaicx_config_init(&cfg);   /* always first */
cfg.license_path = "/etc/xcaicx/unit.token";
cfg.state_dir    = "/var/lib/xcaicx";
cfg.modules      = XCAICX_MODULE_DETECT | XCAICX_MODULE_PPE;

xcaicx_engine *engine;
if (xcaicx_engine_create(&cfg, &engine) != XCAICX_OK) {
    fprintf(stderr, "%s\n", xcaicx_last_error());
    return 1;
}

/* in your ISP callback — borrowed pixels, never copied */
xcaicx_frame f = { .struct_size = sizeof f, .data = nv12,
                   .data_len = len, .width = 1920, .height = 1080,
                   .format = XCAICX_PIX_NV12 };
const xcaicx_result *res;
if (xcaicx_stream_process(stream, &f, &res) == XCAICX_OK)
    for (size_t i = 0; i < res->detection_count; ++i)
        printf("%s %.2f\n", res->detections[i].class_name,
                              res->detections[i].score);
 Python — the deployment path
import xcaicx

with xcaicx.Engine(license_path="unit.token",
                  state_dir="/var/lib/xcaicx") as eng:
    print(eng.license.sku, eng.license.entitlements)

    with eng.stream("line-3",
                    modules=xcaicx.Module.DETECT | xcaicx.Module.PPE) as s:
        s.add_zone("packing-bay",
                   [(0.55, 0), (1, 0), (1, 1), (0.55, 1)],
                   dwell_seconds=30)
        s.add_line("doorway", (0.5, 0), (0.5, 1))

        result = s.process(frame_bgr, width=1920, height=1080,
                           fmt=xcaicx.PixelFormat.BGR8)
        for ev in result.events:
            print(ev.kind.name, ev.zone_name, ev.detail)

Builds where your BSP builds

CMake and C++17, with OpenSSL the only hard dependency — and it is already in every BSP. Cross-compiling is a standard toolchain file. Yocto and Buildroot recipes need openssl in DEPENDS and nothing else.

Backends are one interface

Reference and ONNX Runtime ship in the box. TensorRT, RKNN, OpenVINO and Hailo mean implementing a single header — backend.hpp is the whole extension surface.

Air-gapped is a first-class path

Factories that will never let a camera reach the internet are a large share of the industrial market. Pre-provision device-bound tokens offline and the camera never touches a network; usage still accrues locally for reconciliation.

Degrade, do not die

A lapsed licence stops analytics, never the video path. A camera that stops recording because a licence expired is a warranty claim, so the SDK is built so that cannot happen.

Questions

The things integrators actually ask first

Does anything leave the camera?

Inference does not. The whole pipeline runs on the unit, and a camera with offline = 1 never opens a socket. What optionally leaves is licensing and metering: an activation call on first boot, and a periodic heartbeat carrying counts — inferences, frames, stream-seconds, events. No frames, no crops, no imagery of any kind.

What accuracy should we expect?

From the reference backend that ships in the box: enough to bring up your frame plumbing and event handling, and not enough for production. It is contrast, edge and colour heuristics. Production accuracy comes from building with ONNX Runtime and shipping a model bundle, and the numbers that matter are the ones measured on your cameras in your lighting — which is a conversation, not a datasheet row.

What happens when a camera is offline for a month?

It keeps working. Usage spools to disk and is only cleared after the server acknowledges it, so a long outage delays billing rather than losing it. A licence that expires during the outage enters its grace window; past that, analytics stop and video keeps recording.

Can a customer clone one paid image onto fifty cameras?

Not casually. Licences are bound to a hardware fingerprint, so the copy fails with WRONG_DEVICE on every unit but the original. Bind to fused silicon or a secure element rather than a MAC address and it gets considerably harder — the SDK supports that today through XCAICX_DEVICE_ID. Against an attacker with root and a disassembler, no software scheme wins; see the note in the licensing section.

How is a reflash or an RMA handled?

Re-activating a device already on file returns its existing token and consumes no additional unit, so reflashes and SD-card swaps are free. A genuine hardware replacement is a new fingerprint and therefore a new unit; revoke the old licence if the board is being scrapped.

What does it cost?

Per programme. There are two revenue lines — a per-unit royalty or per-camera subscription, and metered inference over an included allowance — and the shape that fits a 500-unit pilot is not the shape that fits a 100,000-unit run. Tell us the volume, the modules and whether the fleet is online, and you will get real numbers rather than a list price to negotiate down from.

Which SoCs has this run on?

The core is portable C++17 with OpenSSL and no accelerator assumptions, so the reference backend runs anywhere your BSP does. Accelerated inference depends on the backend: ONNX Runtime is in-tree, and TensorRT, RKNN, OpenVINO and Hailo are a single interface each. Bring the board you are shipping and we will talk about what the bring-up actually looks like.

Talk to us

Start with the board you are shipping

The useful first conversation is about your SoC, your volume, your modules and whether the fleet will be online. Evaluation licences are available for the full module set.