OpenCV 计算机视觉完全教程 / 第 04 章 — 绘图与交互
第 04 章 — 绘图与交互
4.1 绘图函数总览
| 函数 | 用途 | 关键参数 |
|---|---|---|
line() | 画线 | pt1, pt2, color, thickness |
rectangle() | 画矩形 | pt1, pt2, color, thickness |
circle() | 画圆 | center, radius, color, thickness |
ellipse() | 画椭圆 | center, axes, angle, startAngle, endAngle |
polylines() | 画多边形 | pts, isClosed, color, thickness |
fillPoly() | 填充多边形 | pts, color |
putText() | 绘制文字 | text, org, fontFace, fontScale, color |
arrowedLine() | 画箭头线 | pt1, pt2, color, thickness |
drawMarker() | 画标记 | position, color, markerType |
注意: 所有绘图函数都是就地操作(in-place),会直接修改传入的图像。如需保留原图,请先
.copy()。
4.2 基本图形绘制
4.2.1 直线与箭头
import cv2
import numpy as np
# 创建画布
canvas = np.zeros((500, 700, 3), dtype=np.uint8)
# 画直线
cv2.line(canvas, (50, 50), (300, 200), (0, 255, 0), 2) # 绿色线
cv2.line(canvas, (50, 100), (300, 250), (255, 0, 0), 5) # 蓝色粗线
# 不同线型(仅 LINE_4 / LINE_8 / LINE_AA)
cv2.line(canvas, (50, 150), (300, 300), (0, 0, 255), 1, cv2.LINE_4)
cv2.line(canvas, (50, 200), (300, 350), (0, 255, 255), 1, cv2.LINE_AA) # 抗锯齿
# 箭头线
cv2.arrowedLine(canvas, (400, 50), (600, 200), (255, 255, 0), 2,
tipLength=0.05)
# 标记点
cv2.drawMarker(canvas, (350, 100), (0, 255, 255),
cv2.MARKER_CROSS, 20, 2)
cv2.drawMarker(canvas, (400, 100), (0, 255, 255),
cv2.MARKER_TILTED_CROSS, 20, 2)
cv2.drawMarker(canvas, (450, 100), (0, 255, 255),
cv2.MARKER_STAR, 20, 2)
4.2.2 矩形
# 空心矩形
cv2.rectangle(canvas, (50, 300), (200, 450), (255, 255, 255), 2)
# 填充矩形(thickness = -1)
cv2.rectangle(canvas, (220, 300), (370, 450), (0, 128, 255), -1)
# 半透明矩形(需要混合操作)
overlay = canvas.copy()
cv2.rectangle(overlay, (390, 300), (540, 450), (255, 0, 128), -1)
cv2.addWeighted(overlay, 0.5, canvas, 0.5, 0, canvas) # 50% 透明
4.2.3 圆与椭圆
# 圆
cv2.circle(canvas, (600, 100), 50, (255, 255, 255), 2) # 空心
cv2.circle(canvas, (600, 250), 40, (0, 255, 0), -1) # 填充
# 椭圆
cv2.ellipse(canvas, (150, 380), (100, 50), 30, 0, 360, (255, 0, 255), 2)
cv2.ellipse(canvas, (150, 380), (60, 30), 30, 0, 180, (0, 255, 255), -1)
# 弧线
cv2.ellipse(canvas, (500, 380), (80, 40), 0, 45, 270, (255, 255, 0), 3)
4.2.4 多边形
# 定义顶点
pts = np.array([[550, 300], [620, 350], [650, 450],
[580, 470], [520, 420]], np.int32)
pts = pts.reshape((-1, 1, 2))
# 空心多边形
cv2.polylines(canvas, [pts], True, (0, 255, 255), 2)
# 填充多边形
pts2 = np.array([[400, 150], [450, 100], [500, 150],
[480, 200], [420, 200]], np.int32)
cv2.fillPoly(canvas, [pts2], (128, 255, 128))
# 凸包多边形(自动排序)
hull = cv2.convexHull(pts)
cv2.polylines(canvas, [hull], True, (0, 0, 255), 1, cv2.LINE_AA)
4.3 文字渲染
4.3.1 putText 基本用法
canvas = np.zeros((400, 600, 3), dtype=np.uint8)
# 基本文字
cv2.putText(canvas, "Hello OpenCV", (50, 50),
cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 255, 255), 2)
# 不同字体
fonts = [
(cv2.FONT_HERSHEY_SIMPLEX, "SIMPLEX"),
(cv2.FONT_HERSHEY_PLAIN, "PLAIN"),
(cv2.FONT_HERSHEY_DUPLEX, "DUPLEX"),
(cv2.FONT_HERSHEY_COMPLEX, "COMPLEX"),
(cv2.FONT_HERSHEY_TRIPLEX, "TRIPLEX"),
(cv2.FONT_HERSHEY_COMPLEX_SMALL, "COMPLEX_SMALL"),
(cv2.FONT_HERSHEY_SCRIPT_SIMPLEX, "SCRIPT"),
(cv2.FONT_HERSHEY_SCRIPT_COMPLEX, "SCRIPT_COMPLEX"),
]
for i, (font, name) in enumerate(fonts):
y = 100 + i * 35
cv2.putText(canvas, name, (50, y), font, 0.7, (0, 255, 0), 1, cv2.LINE_AA)
4.3.2 中文文字渲染
OpenCV 的 putText 不支持中文,需要使用 PIL 辅助:
from PIL import Image, ImageDraw, ImageFont
import cv2
import numpy as np
def put_chinese_text(img, text, position, font_size=32,
color=(255, 255, 255)):
"""在 OpenCV 图像上绘制中文文字"""
# 转换为 PIL Image
img_pil = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(img_pil)
# 加载中文字体
font = ImageFont.truetype("/usr/share/fonts/truetype/"
"noto/NotoSansCJK-Regular.ttc",
font_size)
draw.text(position, text, font=font,
fill=(color[2], color[1], color[0])) # RGB 顺序
# 转回 OpenCV 格式
return cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)
# 使用
canvas = np.zeros((400, 600, 3), dtype=np.uint8)
canvas = put_chinese_text(canvas, "计算机视觉教程", (50, 50), 40)
4.3.3 动态标注框
def draw_label_box(img, text, x, y, color=(0, 255, 0)):
"""绘制带背景的文字标签框"""
font = cv2.FONT_HERSHEY_SIMPLEX
scale = 0.6
thickness = 1
# 计算文字尺寸
(tw, th), baseline = cv2.getTextSize(text, font, scale, thickness)
# 绘制背景矩形
cv2.rectangle(img, (x, y - th - 10), (x + tw + 10, y), color, -1)
# 绘制文字
cv2.putText(img, text, (x + 5, y - 5), font, scale,
(0, 0, 0), thickness, cv2.LINE_AA)
4.4 鼠标事件交互
4.4.1 鼠标事件类型
| 事件 | 常量 | 说明 |
|---|---|---|
| 左键按下 | EVENT_LBUTTONDOWN | 鼠标左键按下 |
| 左键释放 | EVENT_LBUTTONUP | 鼠标左键释放 |
| 右键按下 | EVENT_RBUTTONDOWN | 鼠标右键按下 |
| 鼠标移动 | EVENT_MOUSEMOVE | 鼠标移动 |
| 双击 | EVENT_LBUTTONDBLCLK | 左键双击 |
| 滚轮 | EVENT_MOUSEWHEEL | 滚轮滚动 |
4.4.2 画板应用
"""
draw_app.py — 简易画板应用
"""
import cv2
import numpy as np
# 全局变量
drawing = False
ix, iy = -1, -1
color = (0, 255, 0)
thickness = 2
canvas = np.zeros((600, 800, 3), dtype=np.uint8)
def draw_line(event, x, y, flags, param):
global drawing, ix, iy, canvas
if event == cv2.EVENT_LBUTTONDOWN:
drawing = True
ix, iy = x, y
elif event == cv2.EVENT_MOUSEMOVE:
if drawing:
cv2.line(canvas, (ix, iy), (x, y), color, thickness)
ix, iy = x, y
elif event == cv2.EVENT_LBUTTONUP:
drawing = False
cv2.line(canvas, (ix, iy), (x, y), color, thickness)
# 创建窗口并绑定回调
cv2.namedWindow("画板")
cv2.setMouseCallback("画板", draw_line)
print("操作说明: 鼠标绘图 | c:清除 | r/g/b:颜色 | +/-:粗细 | q:退出")
while True:
cv2.imshow("画板", canvas)
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
break
elif key == ord('c'):
canvas[:] = 0
elif key == ord('r'):
color = (0, 0, 255)
elif key == ord('g'):
color = (0, 255, 0)
elif key == ord('b'):
color = (255, 0, 0)
elif key == ord('+') or key == ord('='):
thickness = min(thickness + 1, 20)
elif key == ord('-'):
thickness = max(thickness - 1, 1)
cv2.destroyAllWindows()
4.4.3 ROI 选择器
"""
roi_selector.py — 交互式 ROI 选择
"""
import cv2
img = cv2.imread("photo.jpg")
clone = img.copy()
roi_pts = []
drawing = False
def mouse_callback(event, x, y, flags, param):
global roi_pts, drawing, img
if event == cv2.EVENT_LBUTTONDOWN:
roi_pts.append((x, y))
drawing = True
elif event == cv2.EVENT_MOUSEMOVE and drawing:
temp = img.copy()
if len(roi_pts) > 0:
cv2.line(temp, roi_pts[-1], (x, y), (0, 255, 0), 2)
cv2.imshow("ROI 选择", temp)
elif event == cv2.EVENT_LBUTTONUP:
drawing = False
cv2.circle(img, (x, y), 3, (0, 0, 255), -1)
cv2.namedWindow("ROI 选择")
cv2.setMouseCallback("ROI 选择", mouse_callback)
print("左键点击选择 ROI 点 | Enter:确认 | c:重置")
while True:
cv2.imshow("ROI 选择", img)
key = cv2.waitKey(1) & 0xFF
if key == 13: # Enter
if len(roi_pts) >= 3:
pts = np.array(roi_pts, np.int32).reshape((-1, 1, 2))
mask = np.zeros(img.shape[:2], dtype=np.uint8)
cv2.fillPoly(mask, [pts], 255)
roi = cv2.bitwise_and(clone, clone, mask=mask)
cv2.imshow("提取 ROI", roi)
print(f"选择了 {len(roi_pts)} 个点的多边形 ROI")
break
elif key == ord('c'):
img = clone.copy()
roi_pts = []
cv2.waitKey(0)
cv2.destroyAllWindows()
4.5 滑块(Trackbar)控件
"""
trackbar_demo.py — 滑块实时调参
"""
import cv2
import numpy as np
img = cv2.imread("photo.jpg")
if img is None:
img = np.random.randint(0, 256, (480, 640, 3), dtype=np.uint8)
def nothing(x):
pass # 回调函数占位
cv2.namedWindow("调整")
cv2.createTrackbar("亮度", "调整", 0, 100, nothing)
cv2.createTrackbar("对比度", "调整", 100, 300, nothing)
cv2.createTrackbar("模糊", "调整", 0, 20, nothing)
while True:
brightness = cv2.getTrackbarPos("亮度", "调整")
contrast = cv2.getTrackbarPos("对比度", "调整")
blur_k = cv2.getTrackbarPos("模糊", "调整")
# 应用亮度和对比度
result = cv2.convertScaleAbs(img, alpha=contrast/100.0,
beta=brightness)
# 应用模糊(核大小必须为奇数)
if blur_k > 0:
k = blur_k * 2 + 1
result = cv2.GaussianBlur(result, (k, k), 0)
cv2.imshow("调整", result)
if cv2.waitKey(30) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
4.6 HSV 颜色选取器
"""
hsv_picker.py — 交互式 HSV 颜色范围选取器
"""
import cv2
import numpy as np
img = cv2.imread("photo.jpg")
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
def nothing(x):
pass
cv2.namedWindow("HSV 选取器")
# H: 0-179, S: 0-255, V: 0-255
cv2.createTrackbar("H_min", "HSV 选取器", 0, 179, nothing)
cv2.createTrackbar("H_max", "HSV 选取器", 179, 179, nothing)
cv2.createTrackbar("S_min", "HSV 选取器", 0, 255, nothing)
cv2.createTrackbar("S_max", "HSV 选取器", 255, 255, nothing)
cv2.createTrackbar("V_min", "HSV 选取器", 0, 255, nothing)
cv2.createTrackbar("V_max", "HSV 选取器", 255, 255, nothing)
while True:
h_min = cv2.getTrackbarPos("H_min", "HSV 选取器")
h_max = cv2.getTrackbarPos("H_max", "HSV 选取器")
s_min = cv2.getTrackbarPos("S_min", "HSV 选取器")
s_max = cv2.getTrackbarPos("S_max", "HSV 选取器")
v_min = cv2.getTrackbarPos("V_min", "HSV 选取器")
v_max = cv2.getTrackbarPos("V_max", "HSV 选取器")
lower = np.array([h_min, s_min, v_min])
upper = np.array([h_max, s_max, v_max])
mask = cv2.inRange(hsv, lower, upper)
result = cv2.bitwise_and(img, img, mask=mask)
# 显示信息
info = f"H:[{h_min},{h_max}] S:[{s_min},{s_max}] V:[{v_min},{v_max}]"
cv2.putText(result, info, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
# 并排显示
mask_bgr = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
combined = np.hstack([img, mask_bgr, result])
cv2.imshow("HSV 选取器", combined)
if cv2.waitKey(30) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
4.7 实战:检测框绘制工具
"""
bbox_drawer.py — 绘制标准检测框(带置信度标签)
"""
import cv2
import numpy as np
def draw_detection(img, bbox, label, confidence, color=(0, 255, 0)):
"""
绘制目标检测标准框
参数:
img: 图像
bbox: (x1, y1, x2, y2) 边界框坐标
label: 类别标签
confidence: 置信度 (0-1)
color: 框颜色 (B, G, R)
"""
x1, y1, x2, y2 = map(int, bbox)
# 绘制边框(角点增强效果)
corner_len = min(20, (x2 - x1) // 4, (y2 - y1) // 4)
t = 2 # 线宽
# 四个角
cv2.line(img, (x1, y1), (x1 + corner_len, y1), color, t)
cv2.line(img, (x1, y1), (x1, y1 + corner_len), color, t)
cv2.line(img, (x2, y1), (x2 - corner_len, y1), color, t)
cv2.line(img, (x2, y1), (x2, y1 + corner_len), color, t)
cv2.line(img, (x1, y2), (x1 + corner_len, y2), color, t)
cv2.line(img, (x1, y2), (x1, y2 - corner_len), color, t)
cv2.line(img, (x2, y2), (x2 - corner_len, y2), color, t)
cv2.line(img, (x2, y2), (x2, y2 - corner_len), color, t)
# 标签背景
text = f"{label} {confidence:.0%}"
font = cv2.FONT_HERSHEY_SIMPLEX
scale = 0.5
(tw, th), baseline = cv2.getTextSize(text, font, scale, 1)
cv2.rectangle(img, (x1, y1 - th - 8), (x1 + tw + 4, y1), color, -1)
cv2.putText(img, text, (x1 + 2, y1 - 4), font, scale,
(0, 0, 0), 1, cv2.LINE_AA)
# 示例
img = np.zeros((500, 700, 3), dtype=np.uint8)
draw_detection(img, (50, 50, 250, 200), "猫", 0.95, (0, 255, 0))
draw_detection(img, (300, 100, 550, 350), "狗", 0.87, (0, 165, 255))
draw_detection(img, (100, 300, 350, 450), "人", 0.92, (255, 0, 0))
4.8 扩展阅读
| 资源 | 链接 | 说明 |
|---|---|---|
| OpenCV 绘图文档 | docs.opencv.org/4.x/d6/d6e/group__imgproc__draw | 绘图函数参考 |
| OpenCV 事件文档 | docs.opencv.org/4.x/d7/dfc/group__highgui | GUI 事件完整列表 |
| PIL 中文字体 | pillow.readthedocs.io | 中文渲染方案 |
| 下一章 | 第 05 章 — 图像滤波 | 模糊/高斯/双边 |
本章小结: 掌握了 OpenCV 全套绘图函数(线条、矩形、圆、多边形、文字),以及鼠标回调、键盘事件、滑块控件等交互方式,为后续的图像标注和可视化奠定了基础。