Skip to content

Trackbars & UI Controls

Trackbars (sliders) are OpenCV’s simplest built-in UI widget for interactive parameter tuning. When you are searching for the right threshold value, the best kernel size, or an optimal color range, a trackbar lets you adjust values in real time and see the result immediately. Combined with OpenCV’s drawing functions, trackbars enable powerful rapid-prototyping tools without any external GUI framework. Every example on this page creates canvases with np.zeros() so you can run them without any external images.

The core function is cv2.createTrackbar:

Python
cv2.createTrackbar(trackbarName, windowName, value, count, onChange)
ParameterDescription
trackbarNameLabel displayed next to the slider.
windowNameName of the existing window to attach the trackbar to.
valueInitial position of the slider.
countMaximum value the slider can reach (minimum is always 0).
onChangeCallback function invoked every time the slider moves. Receives a single int argument — the new position.

OpenCV also provides companion functions for reading and writing trackbar state:

  • cv2.getTrackbarPos(trackbarName, windowName) — returns the current position as an int.
  • cv2.setTrackbarPos(trackbarName, windowName, pos) — moves the slider programmatically.
  • cv2.setTrackbarMin(trackbarName, windowName, minval) — sets a minimum value (OpenCV 3.4+).
  • cv2.setTrackbarMax(trackbarName, windowName, maxval) — changes the maximum value at runtime.
Python
import cv2
import numpy as np
cv2.namedWindow("Brightness")
cv2.createTrackbar("Value", "Brightness", 0, 255, lambda x: None)
while True:
brightness = cv2.getTrackbarPos("Value", "Brightness")
# Create a blank canvas filled with the trackbar value
canvas = np.zeros((300, 400, 3), dtype=np.uint8)
canvas[:] = (brightness, brightness, brightness)
cv2.imshow("Brightness", canvas)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()

Drag the slider from 0 (black) to 255 (white) and the canvas updates instantly.

Every trackbar requires a callback function. It receives a single integer argument — the new slider position. For the simplest cases, pass a no-op lambda:

Python
cv2.createTrackbar("Size", "Window", 5, 100, lambda x: None)

This is perfectly valid. The lambda discards the value, and your main loop reads positions with cv2.getTrackbarPos whenever it needs them.

For more complex interfaces with multiple trackbars, the polling pattern keeps all logic centralized in the main loop instead of scattered across individual callbacks:

Python
import cv2
import numpy as np
cv2.namedWindow("Color Mixer")
cv2.createTrackbar("Red", "Color Mixer", 0, 255, lambda x: None)
cv2.createTrackbar("Green", "Color Mixer", 0, 255, lambda x: None)
cv2.createTrackbar("Blue", "Color Mixer", 0, 255, lambda x: None)
while True:
r = cv2.getTrackbarPos("Red", "Color Mixer")
g = cv2.getTrackbarPos("Green", "Color Mixer")
b = cv2.getTrackbarPos("Blue", "Color Mixer")
canvas = np.zeros((300, 400, 3), dtype=np.uint8)
canvas[:] = (b, g, r) # OpenCV uses BGR order
cv2.putText(canvas, f"RGB({r}, {g}, {b})", (20, 280),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
cv2.imshow("Color Mixer", canvas)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
HSV color range picker showing original image, mask, and filtered result

Practical Example — HSV Color Range Picker

Section titled “Practical Example — HSV Color Range Picker”

One of the most useful trackbar demos is an HSV color range picker. When building color-based segmentation, you need to find the right lower and upper HSV bounds — six trackbars make this interactive.

Python
import cv2
import numpy as np
cv2.namedWindow("HSV Range Picker")
cv2.createTrackbar("H min", "HSV Range Picker", 0, 179, lambda x: None)
cv2.createTrackbar("S min", "HSV Range Picker", 0, 255, lambda x: None)
cv2.createTrackbar("V min", "HSV Range Picker", 0, 255, lambda x: None)
cv2.createTrackbar("H max", "HSV Range Picker", 179, 179, lambda x: None)
cv2.createTrackbar("S max", "HSV Range Picker", 255, 255, lambda x: None)
cv2.createTrackbar("V max", "HSV Range Picker", 255, 255, lambda x: None)
while True:
sample = np.zeros((300, 400, 3), dtype=np.uint8)
cv2.circle(sample, (100, 150), 60, (0, 0, 255), -1)
cv2.rectangle(sample, (180, 80), (280, 220), (0, 255, 0), -1)
cv2.circle(sample, (340, 150), 50, (255, 0, 0), -1)
cv2.ellipse(sample, (200, 260), (80, 30), 0, 0, 360, (0, 255, 255), -1)
h_min = cv2.getTrackbarPos("H min", "HSV Range Picker")
s_min = cv2.getTrackbarPos("S min", "HSV Range Picker")
v_min = cv2.getTrackbarPos("V min", "HSV Range Picker")
h_max = cv2.getTrackbarPos("H max", "HSV Range Picker")
s_max = cv2.getTrackbarPos("S max", "HSV Range Picker")
v_max = cv2.getTrackbarPos("V max", "HSV Range Picker")
lower = np.array([h_min, s_min, v_min])
upper = np.array([h_max, s_max, v_max])
hsv = cv2.cvtColor(sample, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, lower, upper)
result = cv2.bitwise_and(sample, sample, mask=mask)
cv2.imshow("HSV Range Picker", sample)
cv2.imshow("Mask", mask)
cv2.imshow("Filtered Result", result)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()

Adjust the six sliders to isolate specific colors. The mask window shows the binary result of cv2.inRange, and the filtered result shows only the matching pixels.

Practical Example — Real-Time Blur Adjuster

Section titled “Practical Example — Real-Time Blur Adjuster”

Gaussian blur requires an odd kernel size. A trackbar that goes from 1 to 50 can be converted to an odd value with ksize = val * 2 + 1, guaranteeing valid kernels from 3 to 101.

Python
import cv2
import numpy as np
canvas = np.zeros((300, 400, 3), dtype=np.uint8)
cv2.rectangle(canvas, (50, 50), (150, 150), (0, 255, 0), -1)
cv2.circle(canvas, (280, 100), 50, (0, 0, 255), -1)
cv2.putText(canvas, "OpenCV", (100, 250), cv2.FONT_HERSHEY_SIMPLEX, 1.2,
(255, 255, 255), 2)
cv2.namedWindow("Blur Adjuster")
cv2.createTrackbar("Kernel", "Blur Adjuster", 0, 50, lambda x: None)
cv2.createTrackbar("Sigma", "Blur Adjuster", 0, 20, lambda x: None)
while True:
val = cv2.getTrackbarPos("Kernel", "Blur Adjuster")
sigma = cv2.getTrackbarPos("Sigma", "Blur Adjuster")
if val == 0:
blurred = canvas.copy()
else:
ksize = val * 2 + 1
blurred = cv2.GaussianBlur(canvas, (ksize, ksize), sigma)
display = blurred.copy()
cv2.putText(display, f"Kernel: {val*2+1}x{val*2+1} Sigma: {sigma}",
(10, 290), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (200, 200, 200), 1)
cv2.imshow("Blur Adjuster", display)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()

When the kernel trackbar is at 0, no blur is applied. As you increase it, shapes and text progressively soften. Setting sigma to 0 lets OpenCV calculate it automatically from the kernel size.

Canny edge detection at different threshold values showing varying edge sensitivity

Practical Example — Canny Edge Detection Tuner

Section titled “Practical Example — Canny Edge Detection Tuner”

Canny edge detection is highly sensitive to its two threshold parameters. A trackbar interface lets you find optimal values instantly.

Python
import cv2
import numpy as np
canvas = np.zeros((300, 400, 3), dtype=np.uint8)
cv2.rectangle(canvas, (30, 30), (130, 130), (255, 255, 255), 2)
cv2.circle(canvas, (220, 80), 50, (200, 200, 200), -1)
cv2.line(canvas, (300, 20), (380, 140), (150, 150, 150), 3)
cv2.ellipse(canvas, (200, 220), (100, 40), 30, 0, 360, (180, 180, 180), -1)
cv2.putText(canvas, "Edges", (50, 260), cv2.FONT_HERSHEY_SIMPLEX, 1.5,
(120, 120, 120), 3)
gray = cv2.cvtColor(canvas, cv2.COLOR_BGR2GRAY)
cv2.namedWindow("Canny Tuner")
cv2.createTrackbar("Threshold 1", "Canny Tuner", 50, 500, lambda x: None)
cv2.createTrackbar("Threshold 2", "Canny Tuner", 150, 500, lambda x: None)
while True:
t1 = cv2.getTrackbarPos("Threshold 1", "Canny Tuner")
t2 = cv2.getTrackbarPos("Threshold 2", "Canny Tuner")
edges = cv2.Canny(gray, t1, t2)
edges_bgr = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR)
combined = np.hstack([canvas, edges_bgr])
cv2.putText(combined, f"T1: {t1} T2: {t2}", (420, 290),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
cv2.imshow("Canny Tuner", combined)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()

The left half shows the original image and the right half shows detected edges. Lower thresholds produce more edges (including noise), while higher thresholds keep only the strongest gradients. A common guideline is threshold2 roughly 2–3x threshold1.

Practical Example — Combining Mouse Events and Trackbars

Section titled “Practical Example — Combining Mouse Events and Trackbars”

This capstone example builds a mini drawing application combining trackbars for tool configuration, mouse events for interaction, and keyboard shortcuts for tool switching.

Python
import cv2
import numpy as np
canvas = np.zeros((500, 700, 3), dtype=np.uint8)
drawing = False
start_x, start_y = -1, -1
tool = "circle"
def mouse_callback(event, x, y, flags, param):
global drawing, start_x, start_y, canvas
win = "Drawing App"
size = cv2.getTrackbarPos("Brush Size", win)
r = cv2.getTrackbarPos("Red", win)
g = cv2.getTrackbarPos("Green", win)
b = cv2.getTrackbarPos("Blue", win)
color = (b, g, r)
if event == cv2.EVENT_LBUTTONDOWN:
drawing = True
start_x, start_y = x, y
elif event == cv2.EVENT_MOUSEMOVE:
if drawing and tool == "circle":
cv2.circle(canvas, (x, y), size, color, -1)
elif event == cv2.EVENT_LBUTTONUP:
drawing = False
if tool == "line":
cv2.line(canvas, (start_x, start_y), (x, y), color, size)
elif tool == "rectangle":
cv2.rectangle(canvas, (start_x, start_y), (x, y), color, size)
elif tool == "circle":
cv2.circle(canvas, (x, y), size, color, -1)
cv2.namedWindow("Drawing App")
cv2.createTrackbar("Brush Size", "Drawing App", 5, 50, lambda x: None)
cv2.createTrackbar("Red", "Drawing App", 255, 255, lambda x: None)
cv2.createTrackbar("Green", "Drawing App", 255, 255, lambda x: None)
cv2.createTrackbar("Blue", "Drawing App", 255, 255, lambda x: None)
cv2.setMouseCallback("Drawing App", mouse_callback)
while True:
display = canvas.copy()
cv2.putText(display, f"Tool: {tool}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
cv2.putText(display, "Keys: [c]ircle [l]ine [r]ect [s]ave [q]uit", (10, 490),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (150, 150, 150), 1)
cv2.imshow("Drawing App", display)
key = cv2.waitKey(1) & 0xFF
if key == ord("q"):
break
elif key == ord("l"):
tool = "line"
elif key == ord("r"):
tool = "rectangle"
elif key == ord("c"):
tool = "circle"
elif key == ord("s"):
cv2.imwrite("drawing_output.png", canvas)
print("Saved drawing_output.png")
cv2.destroyAllWindows()

Trackbars control brush size and RGB color. The mouse callback reads positions each time it fires, so changes take effect immediately. Mouse events handle drawing — click-and-drag with circle paints freehand, while line and rectangle draw between press and release points. Keyboard shortcuts switch tools: l line, r rectangle, c circle, s save, q quit. This pattern scales well to more complex prototyping tools like annotation editors and ROI selectors.