Video

scalacv.Video
object 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 Mat from frames is borrowed. It is valid from the next() that returned it until you next ask the iterator for anything, and it is released when the frames block 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.toList compiles and gives you N references to one Mat holding the last frame — not N frames.
  • Do read it, and do run the Ops extensions over it: those allocate their own destination and never alias the receiver, so frame.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 Object
trait Matchable
class Any
Self type
Video.type

Members list

Value members

Concrete methods

def frames[A](capture: VideoCapture, attemptsPerFrame: Int = ...)(f: (Iterator[Mat]) => A): A

Runs f over the capture's frames, borrowing a single Mat for the whole traversal.

Runs f over the capture's frames, borrowing a single Mat for the whole traversal.

The iterator decodes into one Mat and overwrites it in place, so the frame handed to each next() is valid only until the next interaction with the iterator, and is released when this method returns — on the exception path too. Read it, or run an Ops operation over it (those allocate their own output and never alias the receiver). Do not retain it, and do not use an Iterator combinator that retains elements: toList, toVector, sliding and buffered all yield references to the same Mat. framesCopied is the version that gives you frames you can keep.

capture is borrowed too: it is neither released nor rewound, so calling frames again resumes from wherever the previous traversal stopped. That is what makes partial consumption — _.take(10) — behave the way it reads.

Value parameters

attemptsPerFrame

how many consecutive failed read() calls end the stream. 1 — the default — is right for a file, where the first false is end-of-file. A live camera can drop a frame without the stream being over, and a small value (2–5) rides that out. It is a bound, not a retry-forever: read blocks in native code with no timeout of its own (see CaptureOptions), so an unbounded loop would turn a dead camera into a hung thread that also spins.

Attributes

Throws
CvError.NativeCall

if OpenCV fails while decoding. End-of-stream is not an error and does not throw.

Source
Video.scala
def framesCopied[A](capture: VideoCapture, attemptsPerFrame: Int = ...)(f: (Iterator[Managed[Mat]]) => A): A

As frames, but each frame is cloned into a caller-owned Managed.

As frames, but each frame is cloned into a caller-owned Managed.

The copy is what makes the frame keepable: it has its own pixel buffer, so it stays valid after the iterator moves on and after this method returns. The price is one allocation and one full-frame copy per frame, which is why it is not the default.

The clone happens as you pull, not up front — frames you never reach are never copied. Everything you do pull is yours to release; Using.Manager or a .use per frame is the way to not forget.

val firstThree = Video.framesCopied(c)(_.take(3).toVector)
try firstThree.foreach(m => process(m.get))
finally firstThree.foreach(_.release())

Attributes

Source
Video.scala
def info(capture: VideoCapture): CaptureInfo

What the backend claims about capture. Every field is advisory — see CaptureInfo.

What the backend claims about capture. Every field is advisory — see CaptureInfo.

Attributes

Source
Video.scala
def open(index: Int): Either[CvError, Managed[VideoCapture]]

Opens a camera by device index, with CaptureOptions.Default.

Opens a camera by device index, with CaptureOptions.Default.

Attributes

Source
Video.scala
def open(index: Int, options: CaptureOptions): Either[CvError, Managed[VideoCapture]]

Opens a camera by device index.

Opens a camera by device index.

Attributes

Returns

Left if the device does not exist, is in use, or no backend can drive it — isOpened is checked, so a capture that cannot deliver frames is an error here and never a silently empty stream. The returned Managed is caller-owned; prefer .use.

Source
Video.scala
def open(source: String): Either[CvError, Managed[VideoCapture]]

Opens a file, URL or device path, with CaptureOptions.Default.

Opens a file, URL or device path, with CaptureOptions.Default.

Attributes

Source
Video.scala
def open(source: String, options: CaptureOptions): Either[CvError, Managed[VideoCapture]]

Opens a file, URL or device path.

Opens a file, URL or device path.

Value parameters

source

whatever the backend understands: a filesystem path, an rtsp:// or http:// URL, a frame_%04d.png sequence pattern, a GStreamer pipeline. OpenCV resolves it itself and knows nothing about classpath resources.

Attributes

Returns

Left if the source cannot be opened — a missing file, an unreadable container, no backend for the protocol. isOpened is checked, so this is never a silently empty stream. The returned Managed is caller-owned; prefer .use.

Source
Video.scala