Skip to main content

OpenCV 4.13Scala 3.3 LTSJDK 17+headless

Teach the JVM
to see edges, contours, faces, motion, markers, gestures and depth

A fluent, typed Image pipeline over the complete OpenCV Java bindings. No raw int constants, no GUI toolkit, no apt-get — and every intermediate frees its own native memory before the next call sees it.

add itmvn"com.worxbend::scalacv:0.1.0"

Plus one natives line for your platform — the install page explains why.

  1. capture
  2. greyscale
  3. blur
  4. canny
  5. contours
  6. detect
Image.read("bench.jpg")

Three colour channels per pixel, in OpenCV’s blue-green-red order.

the shape of it

One chain, from file to result

Every operation returns a new Image and releases the one it consumed. There is no release() to forget, because there is nothing left holding a handle by the time the next call runs.

This runs headless, with no image file and no display server — which is also how it is tested:

import scalacv.*

OpenCv.load()

val edges: Either[CvError, Array[Byte]] =
Image
.blank(160, 120, Scalar.White)
.drawRect(Rect(30, 30, 90, 60), Scalar.Black)
.gray
.canny(50, 150)
.bytes(".png")

Image.reading scopes a file to a block and releases it on success, on failure, and on exception — it is the entry point that cannot leak, so start there unless you have a reason not to:

Image.reading("photo.jpg") { img => img.gray.blur(2).canny(80, 160).write("edges.png") }

Detection results cross back as ordinary immutable Scala values, not as live native handles — so there is nothing left for you to free:

Image.reading("shelf.jpg") { img =>
val codes: Seq[QrCode] = img.qrCodes // decoded text and corners, no Mat left open
img.drawText(s"${codes.size} codes found", Point(10, 30))
.write("annotated.png")
}

what is in the box

Batteries, and the wiring diagram

Eight groups of capability, every one of them documented. Follow any chip to its guide.

  • Core imaging

    Read, decode, convert, filter and write. Colour-space conversion, blurs and sharpening, morphology, thresholding, resizing, rotation, warping and cropping — all typed, all releasing their own intermediates.

  • Shapes & measurement

    Find the outlines in a binary image, measure their area and perimeter, fit boxes and ellipses, simplify polygons, and pull straight lines and circles out of an edge map.

  • Detection & deep learning

    Haar cascades and the YuNet DNN face detector, ONNX and Caffe models through OpenCV’s own inference engine, QR codes, ArUco markers, and face recognition by embedding distance.

  • Video & camera

    A high-level Camera and Recorder over VideoCapture and VideoWriter, frame streams you can fold over, and background-subtraction motion detection for a fixed or MJPEG camera.

  • Human sensing

    Body and hand skeletons, head-pose angles from facial landmarks, and a gesture recogniser built on top of them — plus the background blur and virtual backgrounds a call needs.

  • Robotics & 3D vision

    Chessboard calibration and lens undistortion, stereo depth and obstacle maps, sparse and dense optical flow, ORB features, visual odometry, loop closure and an occupancy grid.

  • 2D graphics & charts

    A composable Picture scene graph for overlays — dashed strokes, alpha compositing, text boxes, charts and animated GIFs — that renders down onto an Image without leaking a Mat.

  • Built to ship

    A native-memory model you can reason about, a concurrency story that names what is and is not thread-safe, benchmark-backed performance guidance, and a production deployment guide.

the hard part

Honest about native memory

An OpenCV Mat lives in native memory the JVM garbage collector cannot see and will not free. In the Java API, forgetting release() leaks; releasing twice, or using a released Mat, is a segmentation fault with no Scala stack trace to show you where.

Managed[A] moves that failure earlier and upward. It releases exactly once, and it throws an ordinary Scala exception on use-after-release — in the JVM, before anything reaches JNI:

val m = Managed(new org.opencv.core.Mat(4, 4, org.opencv.core.CvType.CV_8UC1))
m.close()
m.close() // no-op: release happens exactly once
m.use(identity) // throws IllegalStateException, not SIGSEGV

The Mat lifecycle guide explains the whole model — who owns what, which operations borrow rather than take, and how the leak test suite proves it with resident-set measurements rather than assertions about intent.

find your route

Four ways in

Pick the row that describes you. Each is three pages long, in order.

New to computer vision

You know some Scala. You have never written an image pipeline.

  1. Image basicsWhat a pixel, a channel and a colour space really are — five minutes.
  2. Tutorial: count objectsBuild a working thing, one step at a time.
  3. GlossaryEvery term on this site, in plain language.

You already know OpenCV

You have written this in Python or Java and want the Scala idiom.

  1. Coming from OpenCVThe idiom map, and the three deliberate differences.
  2. Getting startedDependency, natives classifier, first pipeline.
  3. ArchitectureThe two tiers, and why the raw Mat is never hidden.

Building something now

You have a task and want the shortest correct route to it.

  1. Choosing an approachWhich detector, which tier, which module.
  2. CookbookRecipes to copy and adapt.
  3. Operations referenceEvery operation, its parameters and its units.

Shipping to production

It works on your laptop. Now it has to survive a week in a container.

  1. Mat lifecycleThe memory model that makes this trustworthy.
  2. ConcurrencyWhat is thread-safe, what is emphatically not.
  3. DeployingImages, natives, health checks, degradation.

no walled garden

The raw API is always one call away

The high-level pipeline covers the common cases. When it does not cover yours, mat borrows the underlying handle and the complete typed org.opencv.* surface is right there — along with a mid-level layer of extension operations that return Managed[Mat] so you keep the safety without the abstraction:

Image.reading("photo.jpg") { img =>
img.mat.cvtColor(ColorConversion.BgrToGray) // mid-level extension → Managed[Mat]
.pipe(_.gaussianBlur(Size(5, 5)))
.pipe(_.canny(80, 160))
.use(Images.encode(_, ".png"))
}

Nothing is hidden and nothing is final: see the low-level guide for the escape hatches, and coming from OpenCV if you already know the C++ or Python names.

learn by building

Five tutorials, easiest first

Ordered by what each one needs from you, not by topic. The first two need nothing at all — they draw their own input, because this repository ships no image files.

TutorialWhat it teachesWhat you need
Count objects in an imagethreshold → contours → countnothing — it draws its own scene
Track a coloured objectHSV masking and centroidsnothing — it draws its own scene
Process a video frame by framecapture, transform, recorda clip or a webcam — or make one
Detect faces in a photoHaar cascades, then YuNeta photograph; no model download
Run a neural networkONNX inference through OpenCVa model file — get one

next

Start where it suits you

Reference: Operations · Enums and constants · Error model · API docs