
NOTICE: All information contained herein is, and remains
the property of TechnoCore Automate.
The intellectual and technical concepts contained
herein are proprietary to TechnoCore Automate and dissemination of this information or reproduction of this material
is strictly forbidden unless prior written permission is obtained
from TechnoCore Automate.
ObjServiceOnvif captures timestamped PNG snapshots from ONVIF-compatible
IP cameras, applies per-camera image processing (crop, light pollution
reduction, timestamp overlay), and provides a continuous change-detection
monitor. All camera and connection parameters are stored in
ObjServiceOnvif.yaml — no credentials are hard-coded.
Tested hardware: Dahua DH-IPC-HDW2230T-AS-S2 (2 MP Eyeball Dome).
HTTP snapshot CGI (/cgi-bin/snapshot.cgi) returns 500 on this model;
RTSP via FFmpeg is the only reliable capture method and is used exclusively.
camera: block — model catalogKeyed by model number. Stores hardware specifications that apply to all
units of that model.
camera:
DH-IPC-HDW2230T-AS-S2:
make: "Dahua"
rtsp_port: 554
rtsp_path: "cam/realmonitor"
snapshot_method: "rtsp" # HTTP CGI returns 500 on this model
...
connection: block — deployed unitsKeyed by IP address. Each entry references a camera: model and carries
connection credentials, stream parameters, crop geometry, and image
processing flags.
connection:
10.0.10.31:
name: "MountainView"
username: "admin"
password: "secret"
model: "DH-IPC-HDW2230T-AS-S2"
channel: 1
subtype: 0
crop: {left: 0.0, top: 0.0, right: 1.0, bottom: 0.625}
light_pollution: true
light_pollution_sky_bottom: 0.45
| Key | Type | Description |
|---|---|---|
name |
string | Human-readable label used in filenames, subfolders, and PIL stamps |
model |
string | Matches a key in the camera: block |
channel / subtype |
int | RTSP stream selector |
crop |
dict | Fractional bounding box (0.0–1.0) applied after capture |
light_pollution |
bool | Enable sky enhancement (default false) |
light_pollution_sky_bottom |
float | Row fraction where sky ends (default 0.5) |
Each captured frame passes through the following stages in order:
RTSP frame → crop → light pollution reduction → timestamp stamp → PNG
_crop_image)Fractional coordinates from the connection crop: dict are multiplied by
the image dimensions to produce a pixel bounding box. Crop is applied
in-place before any other processing so subsequent steps work only on the
retained scene.
_reduce_light_pollution)Enabled per camera via light_pollution: true. The algorithm operates in
the CIE LAB colour space, which is perceptually uniform and separates
luminance from chrominance — making it well suited to night-sky correction.
Natural night-sky light pollution has two distinct signatures:
Luminance glow — a broad, low-frequency brightness gradient across the
sky caused by scattered artificial light (sodium vapour, LED streetlights).
This sits entirely on the L (lightness) axis of LAB.
Colour cast — sodium and mixed LED sources shift the sky towards warm
orange-yellow. In LAB this appears as a positive offset on the A axis
(green ↔ red) and the B axis (blue ↔ yellow). The neutral point of
both axes is 0 (128 in OpenCV's uint8 encoding).
LAB is chosen over RGB/BGR because adjustments to L do not affect perceived
hue or saturation, and shifts on A/B can be steered precisely towards neutral
without desaturating the whole image.
Contrast Limited Adaptive Histogram Equalization (CLAHE)
divides the L channel into small tiles and equalizes each independently,
then interpolates at tile boundaries for smooth transitions.
The clip limit (1.5) caps how steeply any tile's histogram can be
redistributed. A low clip limit suppresses the broad sky glow gradient
without over-amplifying sensor noise or creating halo artefacts around
bright objects. Standard global histogram equalization has no such
restraint and produces over-processed, unnatural results on camera footage.
clahe = cv2.createCLAHE(clipLimit=1.5, tileGridSize=(8, 8))
l_channel = clahe.apply(l_channel)
Above light_pollution_sky_bottom, a 15 % blend towards the LAB neutral
point partially removes the warm cast without stripping natural sky colour:
channel[:sky_row] = (sky * 0.85 + 128 * 0.15).clip(0, 255)
A blend ratio of 15 % is intentionally gentle — it corrects the most
objectionable orange tint while leaving enough colour information for the
scene to read naturally. Stronger ratios (≥ 40 %) produce a cold,
desaturated sky that looks processed.
_stamp_image)Camera name and current datetime are burned into the bottom-left corner
using PIL ImageDraw. White text on a 2-pixel dark shadow ensures
legibility on any background colour.
Each camera writes to its own subfolder under local.documents/camera/:
local.documents/camera/
MountainView/
MountainView_20260222_213048.png
MountainView_20260222_215330.png
...
EagleEye/
EagleEye_20260222_213051.png
...
capture — single snapshotpython3 factory.service/package.core/ObjServiceOnvif.py capture [IP]
Omit IP to capture all configured cameras.
# All cameras
python3 factory.service/package.core/ObjServiceOnvif.py capture
# One camera with credential override
python3 factory.service/package.core/ObjServiceOnvif.py capture 10.0.10.31 \
-u admin -p mypassword
analyse — capture + AI vision descriptionpython3 factory.service/package.core/ObjServiceOnvif.py analyse [IP] \
--prompt "Describe any vehicles or people." \
--model llava
Requires Ollama running locally with the specified vision model pulled.
monitor — continuous change detectionpython3 factory.service/package.core/ObjServiceOnvif.py monitor \
[IP] [-i SECONDS] [-t THRESHOLD] [-a] [--model MODEL]
| Option | Default | Description |
|---|---|---|
-i / --interval |
30 | Seconds between capture cycles |
-t / --threshold |
1.0 | Change score (0–100) above which a frame is kept |
-a / --analyse |
false | Run AI vision description on changed frames |
--model |
llava | Ollama vision model |
Change detection compares the new frame against the previous kept frame
using greyscale 640×360 thumbnails and PIL.ImageChops.difference. The
mean pixel difference, normalised to 0–100, is the change score. Frames
below the threshold are deleted immediately; prev is not updated so the
next cycle compares against the last meaningful baseline.
Background capture (example):
nohup python3 factory.service/package.core/ObjServiceOnvif.py monitor \
--interval 120 >> local.documents/camera/monitor.log 2>&1 &
Param1: camera IP (looks up connection: block in YAML)
Param2: username override (optional)
Param3: password override (optional)
Result1: absolute path to saved PNG, or '' on failure
sudo apt-get install -y ffmpeg
| Package | Purpose |
|---|---|
ffmpy |
FFmpeg wrapper for RTSP capture |
Pillow |
Crop, timestamp overlay, change detection |
opencv-python |
LAB colour space conversion, CLAHE |
numpy |
Array operations for colour cast correction |
typer |
CLI interface |
All packages are in resource.config/requirements.txt.
cythonize -3 -a -i ObjServiceOnvif.py
Compiling /home/axion/projects/axion/factory.service/package.core/ObjServiceOnvif.py because it changed.
[1/1] Cythonizing /home/axion/projects/axion/factory.service/package.core/ObjServiceOnvif.py
Updated: 2026-02-22