102 lines
3.8 KiB
Python
102 lines
3.8 KiB
Python
"""
|
||
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
|