跳到主要内容

绘图功能

OpenCV 提供了丰富的绘图功能,可以在图像上绘制各种几何图形和文字。这些功能在可视化结果、标注图像、创建交互界面等场景中非常有用。

绘图基础

在 OpenCV 中,绘图操作直接在图像数组上进行,会修改原始图像。如果需要保留原始图像,应该先创建副本:

import cv2
import numpy as np

# 创建空白图像(黑色背景)
image = np.zeros((400, 600, 3), dtype=np.uint8)

# 或者复制已有图像
original = cv2.imread('image.jpg')
image = original.copy()

所有绘图函数都有一些共同的参数:

img:目标图像,绘图操作会直接修改这个图像。

color:颜色,对于彩色图像是 BGR 格式的元组,如 (255, 0, 0) 表示蓝色。对于灰度图像是单个数值。

thickness:线条粗细,默认为 1。设为 -1 表示填充图形内部。

lineType:线条类型,影响线条的平滑程度:

  • cv2.LINE_8:8 连通线(默认)
  • cv2.LINE_4:4 连通线
  • cv2.LINE_AA:抗锯齿线,线条更平滑

绘制直线

使用 cv2.line() 函数绘制直线:

import cv2
import numpy as np

image = np.zeros((400, 600, 3), dtype=np.uint8)

# 绘制蓝色直线
cv2.line(image, (0, 0), (600, 400), (255, 0, 0), 2)

# 绘制绿色抗锯齿直线
cv2.line(image, (0, 400), (600, 0), (0, 255, 0), 2, cv2.LINE_AA)

# 绘制粗红色直线
cv2.line(image, (300, 0), (300, 400), (0, 0, 255), 5)

cv2.imshow('Lines', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

参数说明:

  • pt1:起点坐标 (x, y)
  • pt2:终点坐标 (x, y)
  • color:线条颜色
  • thickness:线条粗细

绘制矩形

使用 cv2.rectangle() 函数绘制矩形:

import cv2
import numpy as np

image = np.zeros((400, 600, 3), dtype=np.uint8)

# 绘制空心矩形(蓝色边框)
cv2.rectangle(image, (50, 50), (200, 150), (255, 0, 0), 2)

# 绘制填充矩形(绿色填充)
cv2.rectangle(image, (250, 50), (400, 150), (0, 255, 0), -1)

# 绘制红色边框矩形
cv2.rectangle(image, (450, 50), (550, 150), (0, 0, 255), 3)

cv2.imshow('Rectangles', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

参数说明:

  • pt1:矩形左上角坐标
  • pt2:矩形右下角坐标
  • thickness:边框粗细,-1 表示填充

矩形绘制常用于目标检测结果的可视化,标注检测到的物体位置。

绘制圆形

使用 cv2.circle() 函数绘制圆形:

import cv2
import numpy as np

image = np.zeros((400, 600, 3), dtype=np.uint8)

# 绘制空心圆
cv2.circle(image, (150, 200), 80, (255, 0, 0), 2)

# 绘制填充圆
cv2.circle(image, (300, 200), 80, (0, 255, 0), -1)

# 绘制抗锯齿圆
cv2.circle(image, (450, 200), 80, (0, 0, 255), 2, cv2.LINE_AA)

cv2.imshow('Circles', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

参数说明:

  • center:圆心坐标 (x, y)
  • radius:圆的半径
  • thickness:边框粗细,-1 表示填充

圆形常用于标注特征点、人脸检测结果等场景。

绘制椭圆

使用 cv2.ellipse() 函数绘制椭圆:

import cv2
import numpy as np

image = np.zeros((400, 600, 3), dtype=np.uint8)

# 绘制完整椭圆
cv2.ellipse(image, (150, 200), (100, 50), 0, 0, 360, (255, 0, 0), 2)

# 绘制旋转椭圆(旋转角度 45 度)
cv2.ellipse(image, (300, 200), (100, 50), 45, 0, 360, (0, 255, 0), 2)

# 绘制椭圆弧(从 0 度到 180 度)
cv2.ellipse(image, (450, 200), (100, 50), 0, 0, 180, (0, 0, 255), 2)

# 绘制填充椭圆
cv2.ellipse(image, (300, 350), (80, 40), 0, 0, 360, (255, 255, 0), -1)

cv2.imshow('Ellipses', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

参数说明:

  • center:椭圆中心坐标
  • axes:椭圆半轴长度 (长轴, 短轴)
  • angle:椭圆旋转角度(度)
  • startAngle:起始角度
  • endAngle:结束角度

椭圆绘制在人脸检测、姿态估计等任务中常用于标注检测结果。

绘制多边形

使用 cv2.polylines() 函数绘制多边形:

import cv2
import numpy as np

image = np.zeros((400, 600, 3), dtype=np.uint8)

# 定义多边形顶点
pts = np.array([[100, 50], [200, 150], [150, 300], [50, 300], [0, 150]], np.int32)

# 重塑为 (n, 1, 2) 形状
pts = pts.reshape((-1, 1, 2))

# 绘制空心多边形
cv2.polylines(image, [pts], True, (255, 0, 0), 2)

# 绘制填充多边形
pts2 = np.array([[350, 50], [500, 100], [450, 300], [300, 250]], np.int32)
pts2 = pts2.reshape((-1, 1, 2))
cv2.fillPoly(image, [pts2], (0, 255, 0))

cv2.imshow('Polygons', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

参数说明:

  • pts:多边形顶点数组列表
  • isClosed:是否闭合多边形
  • thickness:线条粗细

多边形绘制常用于图像分割结果的标注,以及标注不规则形状的目标区域。

绘制文字

使用 cv2.putText() 函数在图像上绘制文字:

import cv2
import numpy as np

image = np.zeros((400, 600, 3), dtype=np.uint8)

# 绘制基本文字
cv2.putText(image, 'Hello OpenCV', (50, 100),
cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)

# 不同字体
fonts = [
cv2.FONT_HERSHEY_SIMPLEX,
cv2.FONT_HERSHEY_PLAIN,
cv2.FONT_HERSHEY_DUPLEX,
cv2.FONT_HERSHEY_COMPLEX,
cv2.FONT_HERSHEY_TRIPLEX,
cv2.FONT_HERSHEY_COMPLEX_SMALL
]

font_names = ['SIMPLEX', 'PLAIN', 'DUPLEX', 'COMPLEX', 'TRIPLEX', 'COMPLEX_SMALL']

for i, (font, name) in enumerate(zip(fonts, font_names)):
cv2.putText(image, name, (50, 150 + i * 40), font, 0.8, (255, 255, 255), 1)

cv2.imshow('Text', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

参数说明:

  • text:要绘制的文字字符串
  • org:文字左下角坐标 (x, y)
  • fontFace:字体类型
  • fontScale:字体缩放比例
  • thickness:线条粗细

获取文字尺寸

在绘制文字前,可以使用 cv2.getTextSize() 获取文字的尺寸,用于精确布局:

import cv2
import numpy as np

image = np.zeros((200, 600, 3), dtype=np.uint8)

text = 'Center Text'
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 1.5
thickness = 2

# 获取文字尺寸
(text_width, text_height), baseline = cv2.getTextSize(text, font, font_scale, thickness)

# 计算居中位置
image_height, image_width = image.shape[:2]
x = (image_width - text_width) // 2
y = (image_height + text_height) // 2

# 绘制居中文字
cv2.putText(image, text, (x, y), font, font_scale, (255, 255, 255), thickness)

cv2.imshow('Centered Text', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

绘制中文文字

OpenCV 的 putText() 函数不支持中文,需要使用 PIL 库来绘制中文:

import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont

# 创建图像
image = np.zeros((200, 600, 3), dtype=np.uint8)

# 转换为 PIL 图像
pil_image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(pil_image)

# 加载字体(需要指定字体文件路径)
try:
font = ImageFont.truetype('simhei.ttf', 30)
except:
font = ImageFont.load_default()

# 绘制中文
draw.text((50, 80), '中文文字示例', font=font, fill=(255, 255, 255))

# 转换回 OpenCV 格式
image = cv2.cvtColor(np.array(pil_image), cv2.COLOR_RGB2BGR)

cv2.imshow('Chinese Text', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

综合示例

目标检测可视化

import cv2

def draw_detection_result(image, boxes, labels, scores, colors=None):
"""
绘制目标检测结果

参数:
image: 输入图像
boxes: 边界框列表 [(x1, y1, x2, y2), ...]
labels: 标签列表
scores: 置信度列表
colors: 每个类别的颜色
"""
if colors is None:
colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (255, 0, 255)]

for i, (box, label, score) in enumerate(zip(boxes, labels, scores)):
x1, y1, x2, y2 = box
color = colors[i % len(colors)]

# 绘制边界框
cv2.rectangle(image, (x1, y1), (x2, y2), color, 2)

# 准备标签文字
text = f'{label}: {score:.2f}'

# 获取文字尺寸
(text_width, text_height), _ = cv2.getTextSize(
text, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 1)

# 绘制标签背景
cv2.rectangle(image, (x1, y1 - text_height - 10),
(x1 + text_width, y1), color, -1)

# 绘制标签文字
cv2.putText(image, text, (x1, y1 - 5),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1)

return image

# 使用示例
image = cv2.imread('street.jpg')
boxes = [(100, 100, 200, 300), (300, 150, 450, 350)]
labels = ['person', 'car']
scores = [0.95, 0.87]

result = draw_detection_result(image, boxes, labels, scores)
cv2.imshow('Detection Result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()

绘制统计图表

import cv2
import numpy as np

def draw_bar_chart(data, labels, title, width=600, height=400):
"""
绘制简单的柱状图

参数:
data: 数据列表
labels: 标签列表
title: 图表标题
width: 图像宽度
height: 图像高度
"""
image = np.ones((height, width, 3), dtype=np.uint8) * 255

margin = 60
chart_width = width - 2 * margin
chart_height = height - 2 * margin

# 绘制坐标轴
cv2.line(image, (margin, margin), (margin, height - margin), (0, 0, 0), 2)
cv2.line(image, (margin, height - margin), (width - margin, height - margin), (0, 0, 0), 2)

# 计算柱子参数
n = len(data)
bar_width = chart_width // (n * 2)
max_data = max(data)

# 绘制柱子
colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (255, 0, 255)]

for i, (value, label) in enumerate(zip(data, labels)):
x = margin + bar_width + i * bar_width * 2
bar_height = int(value / max_data * chart_height)
y = height - margin - bar_height

color = colors[i % len(colors)]
cv2.rectangle(image, (x, y), (x + bar_width, height - margin), color, -1)

# 绘制数值
cv2.putText(image, str(value), (x + bar_width // 4, y - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1)

# 绘制标签
cv2.putText(image, label, (x, height - margin + 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1)

# 绘制标题
cv2.putText(image, title, (width // 2 - len(title) * 5, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 0), 2)

return image

# 使用示例
data = [85, 92, 78, 95, 88]
labels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']
chart = draw_bar_chart(data, labels, 'Weekly Sales')

cv2.imshow('Bar Chart', chart)
cv2.waitKey(0)
cv2.destroyAllWindows()

图像标注工具

import cv2
import numpy as np

class ImageAnnotator:
def __init__(self, image_path):
self.image = cv2.imread(image_path)
self.clone = self.image.copy()
self.points = []
self.drawing = False

def mouse_callback(self, event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONDOWN:
self.points.append((x, y))

elif event == cv2.EVENT_MOUSEMOVE:
if len(self.points) > 0:
self.image = self.clone.copy()
cv2.polylines(self.image, [np.array(self.points + [(x, y)])],
False, (0, 255, 0), 2)

elif event == cv2.EVENT_RBUTTONDOWN:
if len(self.points) >= 3:
self.image = self.clone.copy()
cv2.fillPoly(self.image, [np.array(self.points)], (0, 255, 0))
self.points = []

def run(self):
cv2.namedWindow('Annotator')
cv2.setMouseCallback('Annotator', self.mouse_callback)

while True:
cv2.imshow('Annotator', self.image)
key = cv2.waitKey(1) & 0xFF

if key == ord('r'):
self.image = self.clone.copy()
self.points = []
elif key == ord('s'):
cv2.imwrite('annotated.jpg', self.image)
elif key == 27:
break

cv2.destroyAllWindows()

annotator = ImageAnnotator('image.jpg')
annotator.run()

小结

本章介绍了 OpenCV 的绘图功能,包括直线、矩形、圆形、椭圆、多边形和文字的绘制。关键要点:

绘图修改原图:所有绘图操作都会直接修改目标图像,如需保留原图请先复制。

坐标系统:OpenCV 使用图像坐标系,原点在左上角,x 向右,y 向下。

颜色格式:OpenCV 使用 BGR 格式,注意与其他库的 RGB 格式区分。

抗锯齿:使用 cv2.LINE_AA 可以获得更平滑的线条效果。

中文支持:OpenCV 原生不支持中文,需要借助 PIL 库实现。

下一章将学习图像处理基础,包括颜色空间转换、几何变换和图像滤波等内容。