Skip to content

Markers & Arrows

Markers and arrows are specialized drawing functions for annotation. Arrows indicate direction or flow, markers highlight specific points of interest. These are essential when visualizing optical flow, keypoints, motion vectors, or building annotated datasets.

Overview of OpenCV markers and arrows: cardinal direction arrows, all marker types, optical flow vectors, and combined annotations

cv2.arrowedLine() draws a line segment with an arrowhead at the endpoint:

Python
cv2.arrowedLine(img, pt1, pt2, color, thickness=1, line_type=cv2.LINE_8, shift=0, tipLength=0.1)
  • pt1 — start point (tail), pt2 — end point (tip)
  • tipLength — arrowhead length as a fraction of total line length (default 0.1)
Python
import cv2
import numpy as np
canvas = np.zeros((400, 400, 3), dtype=np.uint8)
cx, cy, length = 200, 200, 120
cv2.arrowedLine(canvas, (cx, cy), (cx, cy - length), (0, 255, 0), 2, tipLength=0.15) # Up
cv2.arrowedLine(canvas, (cx, cy), (cx, cy + length), (0, 0, 255), 2, tipLength=0.15) # Down
cv2.arrowedLine(canvas, (cx, cy), (cx - length, cy), (255, 0, 0), 2, tipLength=0.15) # Left
cv2.arrowedLine(canvas, (cx, cy), (cx + length, cy), (255, 255, 0), 2, tipLength=0.15) # Right
cv2.imshow("Cardinal Arrows", canvas)
cv2.waitKey(0)
cv2.destroyAllWindows()

Paired points representing before/after positions are naturally visualized with arrows:

Python
import cv2
import numpy as np
canvas = np.zeros((400, 500, 3), dtype=np.uint8)
motions = [
((80, 300), (150, 200)),
((200, 350), (250, 250)),
((320, 280), (400, 220)),
((100, 100), (180, 130)),
]
for start, end in motions:
cv2.circle(canvas, start, 3, (100, 100, 100), -1)
cv2.arrowedLine(canvas, start, end, (0, 255, 255), 2, tipLength=0.2)
cv2.imshow("Motion Vectors", canvas)
cv2.waitKey(0)
cv2.destroyAllWindows()

cv2.drawMarker() places a predefined marker symbol at a given point:

Python
cv2.drawMarker(img, position, color, markerType=cv2.MARKER_CROSS, markerSize=20, thickness=1)

OpenCV provides seven marker types:

ConstantDescription
MARKER_CROSSCross + (default)
MARKER_TILTED_CROSS45-degree rotated cross x
MARKER_STAR8-pointed star (cross + tilted cross)
MARKER_DIAMOND / MARKER_SQUAREDiamond / axis-aligned square
MARKER_TRIANGLE_UP / MARKER_TRIANGLE_DOWNUpward / downward triangle
Python
import cv2
import numpy as np
canvas = np.zeros((200, 700, 3), dtype=np.uint8)
markers = [
(cv2.MARKER_CROSS, "CROSS"), (cv2.MARKER_TILTED_CROSS, "TILTED"),
(cv2.MARKER_STAR, "STAR"), (cv2.MARKER_DIAMOND, "DIAMOND"),
(cv2.MARKER_SQUARE, "SQUARE"), (cv2.MARKER_TRIANGLE_UP, "TRI UP"),
(cv2.MARKER_TRIANGLE_DOWN, "TRI DOWN"),
]
for i, (marker_type, label) in enumerate(markers):
x = 50 + i * 90
cv2.drawMarker(canvas, (x, 80), (0, 255, 0), marker_type, markerSize=30, thickness=2)
cv2.putText(canvas, label, (x - 30, 140), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (200, 200, 200), 1)
cv2.imshow("Marker Types", canvas)
cv2.waitKey(0)
cv2.destroyAllWindows()

Simulate detected keypoints (e.g., corners) and mark them on a canvas:

Python
import cv2
import numpy as np
canvas = np.zeros((400, 400, 3), dtype=np.uint8)
cv2.rectangle(canvas, (50, 50), (180, 180), (80, 80, 80), -1)
cv2.rectangle(canvas, (220, 200), (370, 350), (60, 60, 60), -1)
keypoints = [(50, 50), (180, 50), (50, 180), (180, 180),
(220, 200), (370, 200), (220, 350), (370, 350)]
for pt in keypoints:
cv2.drawMarker(canvas, pt, (0, 0, 255), cv2.MARKER_TILTED_CROSS, markerSize=15, thickness=2)
cv2.imshow("Detected Keypoints", canvas)
cv2.waitKey(0)
cv2.destroyAllWindows()

Practical Example — Annotating an Image with Multiple Elements

Section titled “Practical Example — Annotating an Image with Multiple Elements”

A complete annotation combines drawing primitives: rectangles for regions of interest, arrows pointing between them, markers on key points, and text labels.

Python
import cv2
import numpy as np
canvas = np.zeros((500, 700, 3), dtype=np.uint8)
cv2.rectangle(canvas, (50, 100), (250, 300), (0, 255, 0), 2)
cv2.rectangle(canvas, (400, 150), (600, 350), (255, 0, 0), 2)
cv2.putText(canvas, "Region A", (80, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
cv2.putText(canvas, "Region B", (430, 140), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 0, 0), 2)
cv2.arrowedLine(canvas, (255, 200), (395, 250), (0, 255, 255), 2, tipLength=0.08)
cv2.putText(canvas, "flow", (300, 210), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1)
for pt in [(100, 150), (200, 250), (150, 200)]:
cv2.drawMarker(canvas, pt, (0, 200, 200), cv2.MARKER_STAR, markerSize=15, thickness=1)
for pt in [(450, 200), (550, 300), (500, 260)]:
cv2.drawMarker(canvas, pt, (200, 200, 0), cv2.MARKER_DIAMOND, markerSize=15, thickness=1)
cv2.arrowedLine(canvas, (150, 400), (150, 260), (255, 255, 255), 1, tipLength=0.15)
cv2.putText(canvas, "centroid", (90, 430), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
cv2.imshow("Full Annotation", canvas)
cv2.waitKey(0)
cv2.destroyAllWindows()

Practical Example — Visualizing Optical Flow Vectors

Section titled “Practical Example — Visualizing Optical Flow Vectors”

Given arrays of points before and after movement, arrows show displacement. Color coding by magnitude helps distinguish fast from slow motion:

Python
import cv2
import numpy as np
canvas = np.zeros((500, 500, 3), dtype=np.uint8)
np.random.seed(42)
prev_pts = np.random.randint(50, 450, size=(40, 2))
dx = np.random.randint(5, 50, size=(40, 1))
dy = np.random.randint(-20, 20, size=(40, 1))
next_pts = prev_pts + np.hstack([dx, dy])
for i in range(len(prev_pts)):
x1, y1 = int(prev_pts[i][0]), int(prev_pts[i][1])
x2, y2 = int(next_pts[i][0]), int(next_pts[i][1])
magnitude = np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
# Map magnitude to color: low motion (blue) -> high motion (red)
ratio = min(magnitude / 60.0, 1.0)
color = (int(255 * (1 - ratio)), 0, int(255 * ratio))
cv2.circle(canvas, (x1, y1), 2, (100, 100, 100), -1)
cv2.arrowedLine(canvas, (x1, y1), (x2, y2), color, 1, tipLength=0.25)
cv2.putText(canvas, "Blue = low motion", (10, 470), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)
cv2.putText(canvas, "Red = high motion", (10, 490), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
cv2.imshow("Optical Flow Vectors", canvas)
cv2.waitKey(0)
cv2.destroyAllWindows()