强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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__highguiGUI 事件完整列表
PIL 中文字体pillow.readthedocs.io中文渲染方案
下一章第 05 章 — 图像滤波模糊/高斯/双边

本章小结: 掌握了 OpenCV 全套绘图函数(线条、矩形、圆、多边形、文字),以及鼠标回调、键盘事件、滑块控件等交互方式,为后续的图像标注和可视化奠定了基础。