Design principles¶
cv2u is what you wish OpenCV had, in the place you would look for it: import cv2u as cv gives you all of
OpenCV plus a small set of extensions. These principles decide what goes into the library and how it looks.
Several of them are enforced by the test suite.
1. Fill the gaps in OpenCV¶
Add a function only when OpenCV lacks it, or when doing it with OpenCV takes boilerplate that everyone rewrites. Never re-implement something OpenCV already does well.
| cv2u | The gap in OpenCV |
|---|---|
imreadEx, imwriteEx |
cv.imread and cv.imwrite fail on non-ASCII paths (e.g. Korean) on Windows. |
rotateEx |
cv.rotate only rotates by multiples of 90°, and cv.warpAffine crops the corners. |
resizeEx, letterbox |
cv.resize needs the full target size; keeping the aspect ratio or padding is manual. |
hconcatEx, vconcatEx |
cv.hconcat/cv.vconcat require the same height/width, channels and type. |
overlayImage |
No function pastes a transparent (BGRA) image onto another, with clipping at the borders. |
fourPointTransform |
Warping a quadrilateral to a rectangle means ordering the corners and computing the size by hand. |
autoCanny |
cv.Canny needs two hand-tuned thresholds for every image. |
readFrames, writeVideo, videoInfo |
cv.VideoCapture/cv.VideoWriter need a read loop, release(), fourcc codes and many get() calls. |
imshowInline, imshowEx |
cv.imshow does not work in Jupyter, and shows large images cropped by the screen. |
toPillow, fromPillow |
Converting to/from Pillow needs channel reordering, and np.asarray gets palette, CMYK, premultiplied-alpha and big-endian 16-bit images wrong. |
imencodeBase64, imdecodeBase64 |
Web APIs exchange images as base64 or data URIs; OpenCV only encodes to bytes. |
drawLabel, overlayMask |
A detector-style labeled box or a translucent mask takes getTextSize math and blending code. |
cropRect |
Slicing with boxes that extend outside the image silently returns wrong or empty regions. |
montage |
No function arranges many images in a grid. |
equalizeHistEx |
cv.equalizeHist rejects color images. |
inRangeEx |
cv.inRange silently selects nothing for hue ranges that wrap around, such as red. |
unsharpMask |
The standard sharpening filter has no OpenCV function. |
toGray, toBGR, toBGRA |
cv.cvtColor raises an error when the image already has the target format. |
findContoursEx |
cv.findContours rejects color images and returns the contours unsorted. |
fillHoles, removeSmallObjects |
Basic mask clean-up has no OpenCV function. |
contourStats, filterContours, sortContours |
OpenCV measures one property per call (contourArea, arcLength, boundingRect, convexHull...); "sort left to right" and "keep the round ones" are rewritten in every project. |
contoursToMask, cropContour, drawContoursEx |
cv.drawContours needs an image, -1, a color and cv.FILLED to make a mask; cropping an object with its outside blanked, or drawing contours in distinct colors with labels, is bookkeeping. |
approxContour, mergeContours, boundingRects, asContour |
cv.approxPolyDP takes an absolute tolerance; merging pieces, boxing every contour and the (N, 1, 2) int32 layout are boilerplate. |
findTemplate |
cv.matchTemplate returns a score map, not the matches. |
cropToContent |
Trimming a uniform border takes thresholding and bounding-box code. |
imwriteEx(..., makeDirs=True) |
cv.imwrite silently returns False when the folder does not exist. |
ssim, skeletonize, whiteBalance |
They exist only in the quality, ximgproc and xphoto contrib modules, which opencv-python does not include. |
hexToBGR, bgrToHex |
Colors from the web are #rrggbb; OpenCV wants BGR tuples. |
adjustBrightnessContrast, adjustHSV, autoContrast |
cv.convertScaleAbs scales around 0 and takes absolute values; HSV edits need the hue/2 convention and wrap-around; cv.normalize is defeated by a single outlier. |
sharpnessScore, diffMask |
Blur detection and change detection are a few lines everyone rewrites. |
translate, padToMultiple, padToSize, splitTiles, mergeTiles |
Shifting, padding and tiling all need warpAffine/copyMakeBorder arithmetic. |
drawDashedLine, drawDashedRectangle, drawHistogram |
OpenCV draws only solid lines, and cv.calcHist only returns numbers. |
largestComponent, cutout |
Keeping the main object and cutting it out with transparency take component and alpha bookkeeping. |
transformPoints |
cv.transform and cv.perspectiveTransform need (N, 1, 2) float arrays and a different function per matrix type. |
cropRotatedRect, deskew |
OpenCV describes rotated boxes (cv.minAreaRect) but cannot extract one, and has no skew estimation for pages. |
flattenAlpha |
cv.COLOR_BGRA2BGR drops the alpha channel, so transparent areas turn into hidden colors (often black). |
matchHistograms, colorTransfer |
cv.equalizeHist only targets a flat histogram; matching another image's tones or colors is manual. |
imageHash, hashDistance |
The img_hash module is in contrib, which opencv-python does not include. |
boxIoU |
Box overlap is computed only inside cv.dnn.NMSBoxes; matching and tracking code rewrites it. |
nms, xywhToXyxy, xyxyToXywh, clipBoxes, scaleBoxes, expandBoxes |
cv.dnn.NMSBoxes wants lists and returns a tuple when empty; mapping boxes back through a letterbox, clipping and padding them are the same arithmetic in every detector script. |
binarize, morphology |
cv.threshold wants 8-bit grayscale and returns a tuple, adaptive thresholding is a separate function; every morphologyEx call starts with getStructuringElement. |
findLines, findCircles, findBlobs |
cv.HoughLinesP / cv.HoughCircles return None or (N, 1, 4) / (1, N, 3) and need an edge or blurred image first; SimpleBlobDetector takes a Params object with a dozen fields. |
matchFeatures, alignImages |
Detector, matcher norm, knnMatch, ratio test, keypoint unpacking and findHomography with RANSAC are the same twelve lines before every registration. |
maskBoundingRect, applyMask, cropCenter |
cv.boundingRect(cv.findNonZero(mask)) fails on empty or color masks; cv.bitwise_and with a mask only gives black; a center crop is index arithmetic. |
drawBoxes, drawPoints, drawGrid, distinctColors |
Drawing many detections or keypoints in distinct colors, with labels, is a loop everyone writes; OpenCV has no palette. |
pixelate, drawRoundedRectangle |
Mosaic anonymization and rounded boxes take resize and arc arithmetic. |
listImages, imreadAll, imwriteAll |
Collecting the image files of a folder (in natural order) is glob and sort boilerplate; cv.imread returns None silently, so a batch loop needs its own error handling. |
mapTiles |
Running a fixed-input model over a large image is split, loop, merge and seam averaging every time. |
readFrameAt |
Reading one frame means open, seek, read and release. |
putTextEx |
cv.putText only has the Hershey fonts, which cover ASCII only. |
imreadUrl |
cv.imread cannot read from a URL. |
imcolor, imcolorLike, makeGradient |
There is no function to create a filled or gradient image. |
gammaCorrection, centerPoint, boldEdges |
Lookup-table, image-moment and morphological-gradient boilerplate. |
dominantColors, colorQuantize, kmeansColors |
cv.kmeans needs reshaping, float conversion and criteria, and its results change from run to run (global RNG). |
peakColors |
Automatic color clustering usually needs scikit-learn, samples the pixels and is slow; cv2u's own histogram-peak method needs neither. |
meanShiftColors, dbscanColors, drawClusters, clusterPoints |
OpenCV has no clustering that finds the number of groups by itself, for colors or for points. |
VideoReader, VideoWriterEx, CameraStream |
cv.VideoCapture/cv.VideoWriter need isOpened() checks, CAP_PROP_* getters, fourcc/size/isColor up front, manual release(), and buffer live streams so a slow consumer reads stale frames. |
extractFrames, trimVideo, concatVideos, resizeVideo, videoToGif, imagesToVideo |
Every "ffmpeg-lite" task (dump frames, cut, join, resize, GIF, time-lapse) is the same read/write loop; OpenCV cannot write GIFs at all. |
detectScenes, sampleFrames, videoThumbnail, FPSCounter, formatTimestamp |
Scene cuts, preview sheets, FPS overlays and HH:MM:SS.mmm stamps are rewritten in every video script. |
addNoise, motionBlur, vignette, jpegArtifacts, solarize, posterize |
Classic photo degradations for augmentation have no OpenCV function (cv.randn needs a preallocated array; directional blur, vignetting, solarize and posterize are rewritten in every project). |
randomCrop, randomFlip, randomRotate, randomScale, randomPerspective |
cv.flip/cv.warpAffine never touch the detection boxes; keeping labels aligned with a random geometric transform is bookkeeping everyone rewrites. |
mosaic, mixup, cutMix, randomErase |
The standard detector/classifier mixing augmentations otherwise require albumentations or a framework-specific loader. |
maskToPolygons, polygonsToMask, rleEncode, rleDecode |
Datasets store masks as COCO polygons or RLE; cv.findContours/cv.fillPoly need int32 (N, 1, 2) arrays, keep one-pixel specks, and OpenCV has no run-length encoding at all. |
boxesToYolo, yoloToBoxes, readYoloLabels, writeYoloLabels, flipBoxes, flipPolygons |
Normalized class cx cy w h label files and keeping annotations aligned with cv.flip are the same arithmetic in every training script. |
polygonIoU, polygonArea, maskIoU, labelMapToColor, colorToLabelMap, drawSegmentation |
cv.intersectConvexConvex only handles convex shapes, cv.contourArea rounds to integers, and coloring / recovering a class-id map has no OpenCV function. |
preprocessImage, postprocessDetections |
cv.dnn.blobFromImage stretches the image and forgets the geometry, and nothing in OpenCV decodes YOLO-style outputs; cv2u letterboxes, keeps the mapping and returns boxes on the original image after per-class NMS. |
loadModel, readClassNames |
cv.dnn.readNet cannot open non-ASCII paths on Windows and reports a missing file as a C++ assertion; cv2u reads ONNX/TFLite/TF models through Python and raises FileNotFoundError. |
inferImage, drawDetections |
Net.forward() returns only the first output and OpenCV has no labeled-detection drawing; cv2u returns every output layer and draws boxes with class names and scores. |
heatmap, applyColorMapEx, colorBar |
cv.applyColorMap only takes an 8-bit map of the image size; overlaying a float attention/saliency map means normalize, convert, resize and addWeighted every time, and there is no legend strip. |
drawArrow, drawRotatedRect, drawPolygon, drawSkeleton, drawTrajectory, drawCrosshair |
cv.arrowedLine sizes the head relative to the arrow length, OpenCV cannot draw its own RotatedRect, polylines/fillPoly need int32 (N, 1, 2) and draw opaque only, and poses, tracks and crosshairs have no drawing function. |
drawTextBlock, drawFPS, drawProgressBar, drawLegend, compareImages, labeledMontage, blendImages |
cv.putText writes one line with no background; HUD panels, before/after views (split, blend, diff) and titled grids are the same boilerplate in every demo; cv.addWeighted blends exactly two images. |
imageStats, isBlank, isBlurry, estimateNoise |
Dataset triage (flat frames, out-of-focus shots, noise level, entropy) needs a handful of numpy formulas per image; cv.meanStdDev/cv.minMaxLoc only cover parts and cv.minMaxLoc rejects color images. |
histogramEx, histogramCompare |
cv.calcHist takes lists of images, channels and ranges per dtype and returns a (bins, 1) column per call; comparing two images needs the same setup twice plus normalization. |
mse, mae, colorfulness, dominantHue |
Only cv.PSNR exists for error metrics (log scale, overflow-prone on uint8 by hand), and there is no colorfulness or dominant-hue measure. |
sauvolaThreshold, niblackThreshold |
Local thresholds for OCR only exist in the ximgproc contrib module (niBlackThreshold), which opencv-python does not ship; here they are built from cv.integral2. |
removeShadow, removeLines, removeSpeckles, enhanceDocument |
Cleaning a photographed page (shadows, table rules, dust) is a well-known chain of dilate/medianBlur/morphologyEx/inpaint calls that every OCR project rewrites. |
detectPage, scanDocument, detectTextLines, detectTextBlocks, estimateSkew, removeBorder |
There is no function from "photo of a document" to an upright page, its text lines or its skew angle; only the primitives (Canny, approxPolyDP, connectedComponentsWithStats). |
findChessboardEx, calibrateCameraEx, undistortEx |
The calibration tutorial is a page of boilerplate: object-point grids, (found, corners) tuples with (N,1,2) shapes, a separate cornerSubPix on a gray copy, and undistort that leaves black borders unless you also call getOptimalNewCameraMatrix and crop the ROI. |
saveCalibration, loadCalibration |
cv.FileStorage writes YAML/XML and cannot open non-ASCII paths on Windows; a JSON file works everywhere. |
stitchImages, stitchPair |
cv.Stitcher.stitch returns a bare integer status and None instead of raising, and the bare warpPerspective stitch cuts off whatever lands outside the first image. |
imageInfo, imreadOriented, imreadResized |
cv.imread must decode the whole file to tell its size, applies the EXIF orientation only for IMREAD_COLOR/IMREAD_GRAYSCALE (not for IMREAD_UNCHANGED or IMREAD_REDUCED_*), and reduced-size decoding is a flag few know. |
imencodeEx, imdecodeEx, convertImageFormat |
cv.imencode returns (ok, array) instead of bytes, cv.imdecode needs np.frombuffer first, and converting a file to JPEG silently drops the alpha channel. |
lightenColor, colorName, textColorFor, rotatePoints, distancePoints, lineIntersection |
OpenCV converts whole images between color spaces but has nothing for one color, and transforms points only through matrices: no distance matrix, corner ordering, flip or line intersection. |
FLIP_MODE_*, DDEPTH_SAME, ALL_CONTOURS, REDUCE_TO_*, WAIT_FOREVER, KEY_* |
Parameters such as flipCode, ddepth, contourIdx, dim and cv.waitKey take magic numbers with no OpenCV constant (OpenCV's own constants like cv.FILLED are used as they are). |
2. Look like OpenCV¶
An extension should feel like it was always part of OpenCV.
- Modules mirror OpenCV's:
core,imgcodecs(reading and writing images),imgproc(image processing),videoio(video files and cameras),highgui(showing images),calib3d(camera calibration) anddnn(inference;cv.dnnis thecv2u.dnnpackage, which passes every OpenCV name through). An extension goes where the OpenCV function it extends lives (resizeExinimgproclikecv.resize). - Names use camelCase like OpenCV. An extended version of an existing OpenCV function gets the
Exsuffix (rotateExforcv.rotate,putTextExforcv.putText). Constants useUPPER_CASE. - Parameters reuse OpenCV's names and conventions: the source image comes first and is called
src, images are 8-bit BGR by default, and OpenCV parameter names are kept (flags,borderMode,borderValue,angleInDegrees,interpolation); sizes are(width, height)likecv.resize. - Results behave like OpenCV's: image-processing functions return new images instead of modifying their
input, while drawing functions (
drawLabel) draw in place and return the image, likecv.rectangle. Readers returnNonewhen the data cannot be decoded, likecv.imread.
Where an extension intentionally differs from a similar OpenCV function, the docstring says so
(for example, cartesianToPolar returns (theta, rho) while cv.cartToPolar returns (magnitude, angle)).
3. Never shadow OpenCV¶
cv2u adds names, it never replaces them. No extension may have the same name as anything in cv2, so
cv.<anything from OpenCV> always means exactly what it means in OpenCV. (Enforced by a test.)
4. Stay light¶
The core depends only on OpenCV (any distribution, chosen with an extra: [opencv], [headless], [contrib] or
[contrib-headless]), numpy and pillow. Heavier dependencies are optional extras that are
imported lazily, with an ImportError that says how to install them (pip install "python-opencv-utils[cluster]").
Environment-specific packages are never required: imshowInline uses IPython only when it is called in a notebook.
Where OpenCV can do the job, it is used instead of an extra: kmeansColors is built on cv.kmeans, so palettes and
color quantization work without scikit-learn.
5. Typed and validated¶
- Every public function has type hints and a docstring. Type checkers and IDEs also see OpenCV's own signatures through cv2u. (Enforced by mypy, pyright and a test.)
- Invalid input raises
ValueErrorwith a message that names the problem, instead of an internal NumPy or OpenCV error. Concretely: an empty image, aNaNor infinite number, a negative size or a color outside the image's dtype range is rejected up front (helpers incv2u/_validate.py); OpenCV flag values are passed through, as OpenCV reports those itself. (Enforced bytests/test_validation.py.)
6. Change carefully¶
Once a function is released, code that uses it should keep working. 1.0.0 reset the API once (renames and signature changes without aliases; see the changelog); from 1.0 on the deprecation rule applies:
- A renamed function keeps its old name as a deprecated alias that emits a
DeprecationWarningfor at least one minor release. - Never change what an existing call returns silently. New behavior goes behind a new parameter whose default keeps the current behavior.
- Output changes are caught by golden-image regression tests.