Video
Video capture: opening a source, and walking its frames without leaking one per iteration.
==Why there is no frame LazyList==
The obvious shape for "the frames of a video" is a lazy sequence, and it is wrong here. LazyList memoises: once evaluated, a cell holds its head forever so that a second traversal is cheap. Applied to frames, that means every Mat the list has ever produced stays reachable — so either nothing is ever released (an unbounded native leak; a 1080p BGR frame is ~6 MB, so a minute at 30 fps is over 10 GB) or frames are released as they are consumed and the list is a field of dangling handles that the next traversal hands back as empty Mats. There is no version of the API where memoisation and per-frame release are both correct. Same argument, verbatim, for Stream, and for any Iterator combinator that retains what it has seen.
So the frame source here is an Iterator that owns exactly one Mat and decodes into it in place. It is created inside a scope, it is released when that scope ends, and it holds one frame's worth of native memory no matter how long the video is.
==The borrowing contract==
This is the one place in scalacv where a Mat you are handed is not yours, and it is the exact opposite of the contract in Ops.scala:
- The
Matfromframesis borrowed. It is valid from thenext()that returned it until you next ask the iterator for anything, and it is released when theframesblock returns. The iterator is retired at that point, so keeping one is inert rather than dangerous. - Do not retain it. Do not put it in a collection.
it.toListcompiles and gives you N references to one Mat holding the last frame — not N frames. - Do read it, and do run the
Opsextensions over it: those allocate their own destination and never alias the receiver, soframe.cvtColor(...)inside the loop is correct and yields a Mat you own. - Need to keep a frame? framesCopied, which clones per frame and hands you a caller-owned Managed.
Video.open("clip.mp4").map { capture =>
capture.use { c =>
Video.frames(c) { frames =>
frames.map(_.cvtColor(ColorConversion.BgrToGray).use(_.findContours().size)).sum
}
}
}
==Exception mode==
VideoCapture.setExceptionMode(true) turns a silent false into a CvException carrying OpenCV's own message, and open uses it: a missing file becomes CvError.NativeCall quoting the path instead of a bare "it did not open". frames deliberately turns it off for the duration of the loop, because OpenCV reports end-of-file through the identical exception it uses for a broken stream — cap.cpp:533 error: (-2:Unspecified error) in function 'grab', measured on a clean five-frame file. With exception mode on there is no way to tell "the video ended" from "the camera was unplugged", so the loop would have to treat every real failure as a normal end. Off, read returning false ends the stream and a genuine decode error still surfaces as CvError.NativeCall.
Attributes
- Source
- Video.scala
- Graph
-
- Supertypes
-
class Objecttrait Matchableclass Any
- Self type
-
Video.type