Step 0: Inventory of Risks
Given the target is clear, your rapid-prototyping of the distributed application infrastructure depends on several risky points.
0) OpenCV cv2
module heavily uses underlying C-based components and python beauty hangs quite often if trying to use cv2.imshow()
facilities and cv2
( external FSA ) window-management & frame-display services.
1) ZeroMQ framework can help you a lot more than to try to enforce data to be cast into (string)
just to use .send_string() / .recv_string()
- may get better here once rather moving the known image ( pxW * pxH )
geometry * RGB-depth inside some smarter BLOB-mapped object ( will speak about this aspect a bit more in architecture outlook below ).
2) Given points 0 & 1, the ZeroMQ infrastructure ( the more in prototyping ) ought become robust against unhandled exceptions ( which would leave zmq.Context()
instances & their associated .Socket()
children hanging on their own on allocated resources in case cv2.imshow()
crashes, as it quite often does so in rapid prototyping loops.
Thus a thoroughful & self-disciplined framing of the code inside a try: except: finally:
handler & explicit initial .setsockopt( zmq.LINGER, 0 )
immediately upon socket instantiation + final .close()
+ context.term()
inside the finally:
handler section are a fair must.
Step 1: Validate the ZeroMQ part by sending a SEQ
of plain int
-s
The best first level of problem isolation is to setup the flow, just to deliver an uncontrolled SEQ
of integers, being .send( )
broadcast from PUB
side.
... # FOR STREAMING, ALWAYS PREFER DESIGNS USING A NONBLOCKING MODE
SEQ += 1
footage_socket.send( SEQ, zmq.NOBLOCK ) # PUB.send( SEQ ) -> *SUB*
...
Unless your receiving side demonstrates it's robust handling of a flow of int-s, it makes no sense to proceed any further.
...
aMsgIN = footage_socket.recv( zmq.NOBLOCK ) # FOR STREAMING, ALWAYS PREFER DESIGNS USING A NONBLOCKING MODE
print "{0:}".format( aMsgIN if len( aMsgIN ) > 0 else "." ),
# sleep(...) # backthrottle the loop a bit
...
Step 2: Decouple .imshow()
from .recv()
data & event-loops
In case your data-pumps work as needed, the remote display starts to make sense as a next target.
ZeroMQ delivers either a complete message ( a BLOB ), or nothing. This is a fact. Next, ZeroMQ does not warrant anyone to deliver error-free. These are the facts, your design has to live with.
The acquisition is the simpler part, just grab the data ( maybe some colourspace conversions may take centrally here, but otherwise, the task is ( for sub 4K / sub 30fps image processing ) typically error-free on this side.
frame
, as acquired above, is a numpy.ndarray
instance. The best performance would be received on sending a hard-coded, binary-mapped BLOB, without any "smart" conversions, as obviously, the frame
is just a big pack of bits ( though there are some more advanced delicacies possible with Zero-copy mechanics of ZeroMQ, but these would make no direct benefit in this stage on the sender side ).
# struct.unpack() / .pack() seems "just"-enough for fast & smart custom Payload protocol designs
DecodeWireMSG_DATA( aMSG_DATA = struct.unpack( "!" + ( aPayloadHEADER + ( ( pxW * pxH ) * RGB_DEPTH * CELL_SIZE ) ) * "I", aMsgIN ) )
The harder part is on the receiver side. If one tries to use the cv2
built-in event-loop engine, hidden inside .imshow()
, this loop will collide with your external logic of reading a flow of updates via .recv()
as published from the PUB
side.
As a reasonable compromise, one may ignore all the "delayed" frame
-s, that did not get processes in sync with the PUB
-side acquisition / broadcast cadency, and just display the most recent one ... using zmq.CONFLATE
option on the ZeroMQ transport infrastructure ( delayed "old" pictures have lost their sense if the purpose of the reconstruction of the flow-of-events is just a visual preception, on the contrary, if the purpose is to document the complete acquisition 1:1, the zmq.CONFLATE
would be discarding frame
instances, which ought get processed, so some other architecture ought be added for such a 1:1 documentographic purpose, best separate from the "just-visual" branch of the flow of data / processing ).
Having done this, the .recv()
( might be a composition of a Poller()
+ .recv()
loop ) will provide the SUB
side an appropriate data-pump, that is independent from the cv2
tools and it's .imshow()
( hidden )-FSA event-loop.
Architecture & Performance Hints:
for higher fps + FullHD / 2K / 4K projects, profile systematically the accumulated processing latencies / delays of the cv2
processing, using zmq.Stopwatch() instances' { .start(), .stop() }
methods.
having hard data, you may in time detect, when additional needs appear to solve some even harder-real-time constraints, so think about:
principally avoid any risk of falling into any uncontrollable black-hole of python garbage collection -- always control { gc.disable() | gc.enable();gc.collect() }
surrounding your critical-path sections and launching an explicit gc.collect()
where your design knows it is feasible.
avoid new memory allocation delays -- may pre-allocate all necessary numpy
arrays and later just enforce numpy
to use for those an inplace mode of data modifications, thus avoiding any further ad-hoc memory management associated wait-states
design the traffic for increased error-immunity with separate, multi-streamed, independent updates of just sections ( stripes ) of the whole large / (colour)-deep image ( remember Zero-Warranty - getting either a complete "fat"-message or None
)
tune-up the .Context()
's performance using zmq.AFFINITY
to map different classes of I/O traffic priorities onto segregated zmq.Context( N )
I/O-threads.
fine-tune zmq.SNDBUF
+ zmq.SNDHWM
on PUB side, if multi-subscribers are expected and zmq.CONFLATE
is not used.
last but not least, may harness numba.jit()
LLVM-pre-compiled acceleration of re-useable code for the critical-path functions ( typically the heavy numpy
processing ), where additional microseconds shaved-off bring their most beneficial effects on your video-processing pipeline, while still remaining in the comfort of pure python ( well, sure, still with some cv2
caveats ).
Some more hints & tricks with cv2
in prototyping phase:
May like this for cv2
based image-processing.
May like this for cv2
simple GUI-interactive parameter tuning of methods.
May like this for cv2
processing pipeline profiling with zmq.Stopwatch()
details down to [usec]