KPN/examples/09_opencv_cellshade/example_hybrid.py
2026-05-12 21:23:33 +02:00

102 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
09_opencv_cellshade/example_hybrid.py
──────────────────────────────────────
Hybrid cell-shading pipeline: C++ nodes handle capture, grayscale conversion,
and edge detection; a Python/numpy function replaces the C++ quantise node;
Python drives the display loop using cv2.
Pipeline:
┌─[py_quantise]──────────────┐
[CaptureNode] ─────┤ ├──[CompositeNode]──result──▶ cv2.imshow
out0=colour └─[ToGrayNode]─[EdgesNode]───┘ edges───▶ cv2.imshow
out1=grey
For a pure-C++ version see main.cpp; for the C++ static-network version see
12_static_cellshade/main.cpp.
Press 'q' or Esc to stop.
"""
import sys
import os
# Adjust path to wherever CMake placed the .so
BUILD_DIR = os.environ.get("KPN_BUILD_DIR",
os.path.join(os.path.dirname(__file__),
"../../build/examples"))
sys.path.insert(0, BUILD_DIR)
import numpy as np
import cv2
import kpn_opencv as kpn
# ── Python node: replace the C++ quantise with numpy ─────────────────────────
# Receives and returns a BGR numpy array (H×W×3 uint8).
def py_quantise(bgr: np.ndarray) -> np.ndarray:
levels = 4
step = 256 // levels
q = (bgr.astype(np.int32) // step) * step + (step // 2)
return q.clip(0, 255).astype(np.uint8)
# ── Build network ─────────────────────────────────────────────────────────────
net = kpn.Network()
net.add("src", kpn.make_capture()) # out0=colour, out1=grey
net.add_node("quant", py_quantise, # Python node — numpy in/out
inputs=["mat"], outputs=["mat"])
net.add("gray", kpn.make_to_gray()) # in0=bgr → out0=gray
net.add("edges", kpn.make_edges()) # in0=gray → out0=edge_mask
net.add("comp", kpn.make_composite()) # in0=edge_mask, in1=colour
# out0=result, out1=edge_mask
# src.colour → py_quantise
net.connect("src", 0, "quant", 0)
# src.grey → to_gray
net.connect("src", 1, "gray", 0)
# gray → edges
net.connect("gray", 0, "edges", 0)
# quantised colour → composite.colour (input slot 1)
net.connect("quant", 0, "comp", 1)
# edge mask → composite.edges (input slot 0)
net.connect("edges", 0, "comp", 0)
net.build()
net.start()
# ── Display loop (drives GUI on this thread) ──────────────────────────────────
cv2.namedWindow("Cell Shade (Python quant)", cv2.WINDOW_NORMAL)
cv2.namedWindow("Edge Mask", cv2.WINDOW_NORMAL)
cv2.resizeWindow("Cell Shade (Python quant)", 1280, 720)
cv2.resizeWindow("Edge Mask", 640, 360)
try:
while True:
# Blocking reads — GIL released while waiting so C++ threads can run
result = net.read("comp", 0) # composite frame (BGR numpy array)
edges = net.read("comp", 1) # edge mask (grayscale numpy array)
cv2.imshow("Cell Shade (Python quant)", result)
cv2.imshow("Edge Mask", cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR))
key = cv2.waitKey(1)
if key in (ord('q'), 27):
break
# Check windows still open
try:
if cv2.getWindowProperty("Cell Shade (Python quant)",
cv2.WND_PROP_VISIBLE) < 1:
break
except cv2.error:
break
finally:
net.stop()
cv2.destroyAllWindows()
del net # let C++ destructor run before nanobind tears down