skip to content

OpenCV

OpenCV is the classical computer-vision toolbox: image and video I/O, filtering, geometric transforms, feature detection, contour analysis, and a DNN module for running trained networks. Vision interviews still expect the classical side, not only deep learning.

on this pageshow

explore

questions

page 1 of 2

In OpenCV, what input does cv2.findContours require and what does it return?

level: juniorimportance: must knowfreq 72%

answer

  1. binary in, not grayscale
  2. non-zero means foreground
  3. two values, not three, since v4
  4. each contour is (N, 1, 2) int32

basics

~20 s

cv2.findContours expects a single-channel 8-bit binary image with white objects on a black background - every non-zero pixel counts as foreground. In OpenCV 4 and 5 it returns two values: a list of contours and a hierarchy array.

solid answer

~40 s

`cv2.findContours(image, mode, method)` takes a **binary** single-channel 8-bit image. It does not threshold for you: it treats every non-zero pixel as foreground, so passing a raw grayscale photo produces garbage rather than an error. You also pass a retrieval mode (`cv2.RETR_EXTERNAL`, `RETR_LIST`, `RETR_CCOMP`, `RETR_TREE`) and an approximation method (`cv2.CHAIN_APPROX_NONE` keeps every boundary pixel, `CHAIN_APPROX_SIMPLE` keeps only segment endpoints, so a rectangle comes back as 4 points). In OpenCV 4 and 5 the call returns `(contours, hierarchy)`; OpenCV 3 returned a third value (the modified image) first, which is why so much old code unpacks three. Each contour is a NumPy array of shape `(N, 1, 2)` with dtype `int32`. Since 3.2 the source image is no longer modified in place.

code

python · 14 lines
python
import cv2
import numpy as np

binary = np.zeros((200, 200), np.uint8)
cv2.rectangle(binary, (20, 20), (80, 80), 255, -1)
cv2.circle(binary, (140, 140), 30, 255, -1)

contours, hierarchy = cv2.findContours(
    binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
)
print(len(contours), contours[0].shape, contours[0].dtype)

canvas = cv2.cvtColor(binary, cv2.COLOR_GRAY2BGR)
cv2.drawContours(canvas, contours, -1, (0, 255, 0), 2)

go deeper

for a junior

Be ready to say the input must be a binary single-channel image, objects white on black, and that the call returns contours plus a hierarchy. Knowing the threshold step comes first is most of the answer.

for a middle

Explain the mode and method flags concretely - RETR_EXTERNAL versus RETR_TREE, CHAIN_APPROX_SIMPLE versus NONE - and describe the (N, 1, 2) int32 contour array and the OpenCV 3 to 4 signature change.

for a senior

Show the production instinct: threshold, morphological clean-up, then contours filtered by area, and explain how an un-thresholded input fails silently rather than raising. Mention that the source is no longer modified in place.

for a principal

Own the pipeline design choice: when a classical contour pipeline is the right tool at all versus a learned detector, what it costs in tuning per lighting condition, and how you keep threshold and kernel parameters from becoming untraceable site-specific magic numbers.

## What a contour is here In OpenCV a contour is a closed curve traced along the boundary of a connected region of non-zero pixels. It is not an edge map and not a list of edge pixels: it is an **ordered** sequence of points that walks around one blob, which is what makes downstream measurement (area, perimeter, bounding box, shape matching) possible. ## The input contract `cv2.findContours` wants a single-channel, 8-bit image. Two rules follow from that: 1. **It must already be binary in spirit.** The function does not threshold. It partitions pixels into background (value 0) and foreground (anything non-zero). Hand it an un-thresholded grayscale frame and it will not raise - it will happily trace the boundary between the few true-zero pixels and everything else, returning a small number of huge, meaningless contours, or thousands of noisy ones. This is the classic silent-wrong-answer on this API. 2. **Objects must be white on black.** If your parts are dark on a light background, you get the *background's* boundary instead. Fix it at threshold time with `cv2.THRESH_BINARY_INV`, or invert with `cv2.bitwise_not`. Since OpenCV 3.2 the input array is no longer overwritten, so the defensive `binary.copy()` you see in older code is unnecessary in OpenCV 4 and 5. ## The two mandatory flags **Retrieval mode** decides *which* contours come back and whether nesting is recorded. `cv2.RETR_EXTERNAL` returns only the outermost contours - the usual choice for counting solid parts. `cv2.RETR_LIST` returns all of them, including the boundaries of holes, with no parent/child relationships. `cv2.RETR_CCOMP` gives a two-level result, and `cv2.RETR_TREE` the full nesting tree. **Approximation method** decides how many points each contour carries. `cv2.CHAIN_APPROX_NONE` stores every single boundary pixel; `cv2.CHAIN_APPROX_SIMPLE` collapses straight horizontal, vertical and diagonal runs to their endpoints, so an axis-aligned rectangle comes back as four points instead of hundreds. `SIMPLE` is the default choice: it is smaller and faster and loses nothing that area, perimeter or bounding-box code cares about. Use `NONE` only when you genuinely need every boundary pixel, for example to sample intensities along the outline. ## The return value In OpenCV 4 and OpenCV 5: ``` contours, hierarchy = cv2.findContours(binary, mode, method) ``` `contours` is a Python list (or tuple) of NumPy arrays. Each array has shape `(N, 1, 2)` and dtype `int32` - N points, each an `(x, y)` pair, with a redundant middle axis that is a legacy of the C++ `vector<Point>` binding. That middle axis is why `cnt[:, 0, :]` or `cnt.reshape(-1, 2)` shows up everywhere, and why `cnt.shape[0]` (not `len(cnt[0])`) is the point count. `hierarchy` is an array of shape `(1, N, 4)` describing nesting; when a mode records no relationships the unused slots are `-1`. If no contours are found, `contours` is empty and `hierarchy` is `None` - guard for that before indexing it. OpenCV 3 returned `(image, contours, hierarchy)`. Interviewers like this detail because it is the single most common breakage when old tutorials are pasted into a modern environment; the symptom is `ValueError: not enough values to unpack`. ## Coordinates and drawing back Contour points are `(x, y)` - column first - while NumPy indexing on the same image is `img[y, x]`. Mixing the two is a routine bug. `cv2.drawContours(image, contours, contourIdx, color, thickness)` takes the **list**, not one contour: to draw a single contour you pass `[cnt]`. `contourIdx=-1` draws all of them, and `thickness=cv2.FILLED` (-1) fills the interior, which is a handy way to rebuild a clean mask from selected contours. Drawing onto a single-channel image with a `(0, 255, 0)` colour silently uses only the first component, so convert to 3-channel first if you want colour. ## Practical sequence The everyday recipe is: grayscale, threshold (global, Otsu or adaptive), a morphological clean-up pass, then `findContours` with `RETR_EXTERNAL` and `CHAIN_APPROX_SIMPLE`, then filter the result by `cv2.contourArea` to drop specks before measuring what survives. Skipping the clean-up step is why naive pipelines report hundreds of one-pixel contours.

  • Your code does contours, hierarchy = cv2.findContours(...) and raises a ValueError about unpacking. What happened?
    You are running against OpenCV 3, which returned three values - the modified image, the contours, and the hierarchy. OpenCV 4 and 5 dropped the image and return two. Either pin the modern version and unpack two, or use the version-agnostic trick of taking the last two elements of the returned tuple.
  • When would you choose CHAIN_APPROX_NONE over CHAIN_APPROX_SIMPLE?
    When you need every boundary pixel rather than just the polygon corners: sampling image intensity along the outline, computing a per-pixel distance profile, or feeding a curvature or chain-code analysis. For area, perimeter, bounding boxes and shape matching, SIMPLE gives the same answers with far fewer points and less memory.
  • How do you turn a selected subset of contours back into a clean binary mask?
    Create a zeroed single-channel array the size of the image and call cv2.drawContours(mask, selected, -1, 255, cv2.FILLED). The FILLED thickness (-1) paints the interior, so you get a mask containing exactly the blobs that passed your filter, ready for bitwise_and against the original.

saying these in an interview costs you the question

  • Thinks findContours thresholds the image for you
  • Says it works on a color image directly
  • Unpacks three return values on OpenCV 4 or 5
  • Assumes dark objects on a light background are found
  • Reads contour points as (row, column) instead of (x, y)

context

open as a page

How do you load an ONNX model with cv2.dnn and run one inference?

level: juniorimportance: must knowfreq 68%

basics

~10 s

Call cv2.dnn.readNet("model.onnx") (or readNetFromONNX), build a blob with cv2.dnn.blobFromImage, pass it in with net.setInput(blob), then call net.forward() to get the output array. No training framework is needed at runtime.

open as a page

In OpenCV, what does detectAndCompute() return for a detector like SIFT or ORB?

level: juniorimportance: must knowfreq 62%

basics

~20 s

detectAndCompute returns a two-element tuple: a list of cv2.KeyPoint objects and a NumPy descriptor array with one row per keypoint. Row i describes keypoints[i]. When nothing is detected, the descriptor value is None, not an empty array.

open as a page

Which cv2.resize interpolation flag should you use when downscaling, and why?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Use cv2.INTER_AREA. It averages over the source pixel area that maps to each output pixel, so detail is integrated rather than skipped. The default cv2.INTER_LINEAR samples only a few points and produces aliasing and moire on large reductions.

open as a page

Why do images read with cv2.imread() look blue when shown by matplotlib?

level: juniorimportance: must knowfreq 88%

basics

~10 s

cv2.imread() returns pixels in BGR channel order, while matplotlib's imshow expects RGB. The first and third channels are interpreted swapped, so reds render as blues. Convert first with cv2.cvtColor(img, cv2.COLOR_BGR2RGB).

open as a page

In OpenCV, what is the difference between MORPH_OPEN and MORPH_CLOSE?

level: middleimportance: must knowfreq 66%

basics

~20 s

MORPH_OPEN erodes then dilates: it removes small white specks and thin bridges. MORPH_CLOSE dilates then erodes: it fills small holes and joins nearby blobs. Both roughly preserve blob size, unlike a bare erode or dilate.

open as a page

In OpenCV, what changes when you add cv2.THRESH_OTSU to cv2.threshold?

level: middleimportance: must knowfreq 62%

basics

~20 s

OpenCV then ignores the threshold value you passed and computes one from the image histogram by maximizing between-class variance. The chosen value comes back as the first return value of cv2.threshold, which is why the call is written with 0 as the threshold argument.

open as a page

What does cv2.dnn.blobFromImage do to an image before inference?

level: middleimportance: must knowfreq 74%

basics

~10 s

cv2.dnn.blobFromImage resizes the image to the network's input size, subtracts a per-channel mean, multiplies by scalefactor, optionally swaps the red and blue channels, and returns a 4-D NCHW float array ready for setInput.

open as a page

How do you turn raw cv2.dnn detection output into final boxes on a frame?

level: middleimportance: must knowfreq 57%

basics

~20 s

Read the model's output layout, keep rows above a confidence threshold, rescale the normalised coordinates by the original frame's width and height, convert them to x, y, w, h, then call cv2.dnn.NMSBoxes to drop overlapping duplicates and draw only the returned indices.

open as a page

Why must matching ORB descriptors use cv2.NORM_HAMMING rather than NORM_L2?

level: middleimportance: must knowfreq 68%

basics

~20 s

ORB emits binary descriptors — 256 comparison bits packed into 32 uint8 bytes — so distance means counting differing bits, which is what cv2.NORM_HAMMING does. NORM_L2 treats those packed bytes as numeric magnitudes, producing meaningless distances. SIFT's float32 vectors need NORM_L2.

open as a page

How does Lowe's ratio test with knnMatch(k=2) filter OpenCV feature matches?

level: middleimportance: must knowfreq 58%

basics

~20 s

knnMatch with k=2 returns the two nearest descriptors for each query. Keeping a match only when the best distance is below roughly 0.75 times the second-best discards ambiguous matches, where a descriptor fits two candidates almost equally well and is therefore untrustworthy.

open as a page

Why does cv2.bilateralFilter preserve edges when cv2.GaussianBlur does not?

level: middleimportance: must knowfreq 62%

basics

~20 s

cv2.GaussianBlur weights neighbours only by distance, so it averages straight across edges. cv2.bilateralFilter multiplies that spatial weight by a second weight based on intensity difference, so pixels on the far side of an edge contribute almost nothing.

open as a page

What do the two threshold arguments of cv2.Canny control?

level: middleimportance: must knowfreq 70%

basics

~20 s

They drive hysteresis. Pixels with gradient magnitude above the upper threshold are accepted as strong edges; below the lower they are discarded; in between they are kept only if they connect to a strong edge. A common ratio is 1:2 to 1:3.

open as a page

What range does the hue channel use after cv2.cvtColor(img, cv2.COLOR_BGR2HSV)?

level: middleimportance: must knowfreq 60%

basics

~20 s

For an 8-bit image, OpenCV stores hue as 0-179, halving the usual 0-360 degrees so it fits in a byte; saturation and value use 0-255. Copying hue values from a colour picker that reports 0-360 selects the wrong colour band.

open as a page

When do you choose Lucas-Kanade over Farneback optical flow in OpenCV?

level: middleimportance: must knowfreq 55%

basics

~20 s

cv2.calcOpticalFlowPyrLK is sparse: you give it specific points and it returns where each moved, plus a status array. cv2.calcOpticalFlowFarneback is dense: it returns a displacement vector for every pixel. Choose sparse when you track a few features cheaply, dense when you need motion everywhere.

open as a page

How does cv2.createBackgroundSubtractorMOG2 label pixels, and what does detectShadows change?

level: middleimportance: must knowfreq 58%

basics

~20 s

MOG2 models each pixel's recent history as a mixture of Gaussians and returns an 8-bit mask: 0 for background, 255 for foreground. With detectShadows=True (the default) it adds a third label, 127, for pixels it judges to be shadow — so a naive nonzero test counts shadows as objects.

open as a page

Why does cv2.VideoWriter produce an empty video file without ever raising an error?

level: middleimportance: must knowfreq 62%

basics

~20 s

VideoWriter.write() silently ignores any frame whose size or channel count differs from the frameSize and isColor passed to the constructor, and the writer never opens at all if the fourcc codec is unavailable. Check isOpened() and pass (width, height).

open as a page

How do you use cv2.findHomography with RANSAC to reject outlier matches?

level: seniorimportance: must knowfreq 55%

basics

~20 s

Pass matched point pairs as (N,1,2) float32 arrays with method cv2.RANSAC and a reprojection threshold in pixels. The call returns the 3x3 homography plus an inlier mask; the inlier count from that mask, not the raw match count, is the signal that the fit is trustworthy.

open as a page

Why is cv2.medianBlur better than cv2.GaussianBlur for salt-and-pepper noise?

level: juniorimportance: should knowfreq 52%

basics

~10 s

Salt-and-pepper noise is isolated extreme pixels. cv2.medianBlur takes the middle value of each neighbourhood, so an outlier is discarded outright. cv2.GaussianBlur averages the outlier in, spreading one bad pixel into a visible grey smudge.

open as a page

What does cv2.imread() return when it cannot read the file, and why?

level: juniorimportance: should knowfreq 56%

basics

~20 s

cv2.imread() returns None on any failure — missing path, unreadable permissions, corrupt data, or an unsupported format. It never raises. The error surfaces later, usually as 'NoneType' object has no attribute 'shape', so check for None immediately after the read.

open as a page

In OpenCV, when does cv2.adaptiveThreshold beat a global threshold?

level: middleimportance: should knowfreq 52%

basics

~20 s

When illumination varies across the frame. cv2.adaptiveThreshold computes a separate threshold for every pixel from the mean of its blockSize x blockSize neighbourhood, minus a constant C, so a shadow or lighting gradient cancels out instead of blacking out half the image.

open as a page

In OpenCV, how do you get each blob's area and centroid from a binary mask?

level: middleimportance: should knowfreq 48%

basics

~20 s

Two routes. Per contour: cv2.moments gives m00 as the area and m10/m00, m01/m00 as the centroid. Per labelled region: cv2.connectedComponentsWithStats returns stats with CC_STAT_AREA in pixels plus a centroids array - remember label 0 is the background.

open as a page

Why does OpenCV's net.forward() with no argument miss a detector's outputs?

level: middleimportance: should knowfreq 52%

basics

~20 s

Called with no argument, cv2.dnn Net.forward returns a single blob — the first output. A multi-head detector such as a Darknet YOLOv3 net has several output layers, so you must pass net.getUnconnectedOutLayersNames() to forward and receive a list of blobs.

open as a page

In OpenCV, how do cv2.cornerHarris and cv2.goodFeaturesToTrack differ?

level: middleimportance: should knowfreq 50%

basics

~10 s

cv2.cornerHarris returns a float32 response map the same size as the input image, which you must threshold yourself. cv2.goodFeaturesToTrack scores corners with the Shi-Tomasi minimum-eigenvalue rule and returns an already-filtered array of corner coordinates.

open as a page

Why does cv2.Sobel with ddepth=cv2.CV_8U lose half of the edges?

level: middleimportance: should knowfreq 48%

basics

~10 s

A derivative is signed: bright-to-dark transitions give negative values. CV_8U cannot hold negatives, so they saturate to 0 and one polarity of every edge vanishes. Compute into CV_16S/CV_32F/CV_64F, then convert with cv2.convertScaleAbs.

open as a page

Which image data does cv2.imread() discard by default, and how do you keep it?

level: middleimportance: should knowfreq 44%

basics

~10 s

The default flag cv2.IMREAD_COLOR always yields a 3-channel 8-bit BGR array: it drops any alpha channel and truncates 16-bit files to 8 bits. cv2.IMREAD_UNCHANGED returns the data as stored, alpha and bit depth included.

open as a page

When should you use cv2.split() instead of NumPy channel indexing on an image?

level: middleimportance: should knowfreq 38%

basics

~20 s

cv2.split() allocates a separate copy of every channel, so use it only when you need standalone single-channel arrays. NumPy indexing like img[:, :, 0] returns a view with no copy — cheaper for reading, and writes through to the original image.

open as a page

How do OpenCV's CSRT and KCF trackers differ, and how do you drive one per frame?

level: middleimportance: should knowfreq 52%

basics

~20 s

Both are single-object trackers seeded once with a box: create with cv2.TrackerCSRT.create() or cv2.TrackerKCF.create(), call init(frame, bbox), then update(frame) each frame for (success, bbox). CSRT is more accurate and handles scale change better; KCF is considerably faster.

open as a page

What does the hierarchy array from cv2.findContours contain, and when do you need RETR_TREE?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Hierarchy has shape (1, N, 4); each row is [Next, Previous, First_Child, Parent] holding indices into the contours list, with -1 meaning none. RETR_TREE is needed when nesting matters - counting holes, or telling an outer boundary from the shapes inside it.

open as a page

In OpenCV DNN, why might DNN_TARGET_CUDA still run inference on the CPU?

level: seniorimportance: should knowfreq 48%

basics

~20 s

setPreferableBackend and setPreferableTarget express a preference, not a requirement. If the binary was not built with CUDA support — pip's opencv-python wheels are not — OpenCV logs a warning and silently falls back to the default CPU implementation, so timings stay flat.

open as a page

showing 1–30 of 36