OpenCV Python 绘图函数实战:cv.line、cv.rectangle、cv.circle、cv.ellipse 与 cv.putText 全解
本篇基于 OpenCV 官方 Python 教程 py_drawing_functions,系统讲解如何使用 OpenCV 的绘制 API 在图像上画出直线、矩形、圆、椭圆、多边形并添加文字。读完本文,你不仅能掌握 cv.line()、cv.rectangle()、cv.circle()、cv.ellipse()、cv.polylines()、cv.putText() 的完整用法与参数含义,还能结合 modules/imgproc/src/drawing.cpp 中的源码实现,理解 thickness 填充规则、LINE_AA 抗锯齿的降级逻辑以及多折线批量绘制的底层机制,从而在实际项目中正确、高效地完成图像标注与结果可视化。
一、绘图函数的公共参数
OpenCV 中所有基础绘图函数(直线、矩形、圆、椭圆、多边形、文字)共享一组核心参数,理解它们是掌握全部绘图 API 的前提(来源:官方教程):
| 参数 | 说明 |
|---|---|
img |
要在其上进行绘制的目标图像,即绘图画布 |
color |
形状颜色。BGR 三通道图像传元组,例如蓝色为 (255,0,0);灰度图像直接传标量值 |
thickness |
线条/圆的粗细。对圆等封闭图形传 -1 时会填充整个形状。默认粗细为 1 |
lineType |
线的类型,如 8 连通线、抗锯齿线等。默认为 8 连通。cv.LINE_AA 提供抗锯齿效果,画曲线时视觉效果好得多 |
从源码结构看,这组参数的行为有明确的实现约束:
- 抗锯齿只对 8 位图生效:在 line() 实现 中可以看到
if( line_type == cv::LINE_AA && img.depth() != CV_8U ) line_type = 8;——当图像不是CV_8U深度时,LINE_AA会被自动降级回 8 连通线。圆、矩形、椭圆、多边形(circle()、rectangle()、ellipse()、polylines())均有同样的降级逻辑,因此抗锯齿主要服务于uint8图像。 LINE_AA的取值:在 modules/imgproc/include/opencv2/imgproc.hpp 中定义为LINE_AA = 16,与教程中"默认为 8 连通"的说法相互印证。- thickness 的合法性检查:
line()中通过CV_Assert( 0 < thickness && thickness <= MAX_THICKNESS )断言约束线宽;而thickness = -1的填充语义由各图形函数分别处理,例如rectangle()在thickness >= 0时走PolyLine描边、否则走FillConvexPoly填充(见 rectangle() 实现)。
二、绘制直线:cv.line()
绘制直线需要传入起点和终点坐标。下面创建一张黑图,并从左上角到右下角画一条蓝色对角线,粗细 5 像素:
import numpy as np
import cv2 as cv
# 创建黑色图像 (512x512, 3通道)
img = np.zeros((512, 512, 3), np.uint8)
# 绘制一条粗细为 5px 的蓝色对角线
cv.line(img, (0, 0), (511, 511), (255, 0, 0), 5)
参数解读:(0,0) 与 (511,511) 是端点坐标,(255,0,0) 为 BGR 蓝色,5 是线宽。从 line() 源码 看,cv.line() 最终通过 ThickLine() 完成粗线光栅化;其姊妹函数 arrowedLine()(L1845-L1864)则通过两条额外的短线段构造箭头尖端,可用于绘制带箭头的标注线。
三、绘制矩形:cv.rectangle()
绘制矩形需要传入左上角与右下角两个坐标。这里在图像右上角画一个绿色矩形:
cv.rectangle(img, (384, 0), (510, 128), (0, 255, 0), 3)
参数解读:(384,0) 和 (510,128) 是矩形的对角点,(0,255,0) 为绿色,3 是线宽。结合 rectangle() 源码 可知,函数内部由两个对角点扩展出四个顶点 pt[0..3],当 thickness >= 0 时按闭合折线描边,传负值时则用 FillConvexPoly 填充整个矩形——即 thickness = -1 得到实心矩形。此外还存在接受 Rect 对象的重载(L1899-L1914),会先把矩形裁剪到图像边界内再绘制,因此画在图像外的目标检测框不会被忽略或报错。
四、绘制圆:cv.circle()
绘制圆需要圆心坐标与半径。教程示例在上一节画的矩形内部绘制一个圆:
cv.circle(img, (447, 63), 63, (0, 0, 255), -1)
参数解读:(447,63) 是圆心,63 是半径,(0,0,255) 为红色,thickness = -1 表示填充,因此画出一个实心红色圆。
从 circle() 源码 可以看到一个有意思的实现细节:cv.circle() 本质上是特例化的椭圆绘制。当 thickness > 1、非 8 连通线或存在坐标偏移时,函数内部将圆心与半径放大到 XY_SHIFT 精度后调用 EllipseEx(img, center, (radius, radius), 0, 0, 360, ...);只有在最轻量的 thickness == 1 && LINE_8 场景下才走专门的 Circle() 快速路径。这也解释了为什么圆的 thickness = -1 填充行为与椭圆一致。
五、绘制椭圆:cv.ellipse()
椭圆是参数最多的绘制函数。需要依次提供:
- 中心坐标
(x, y); - 两轴半轴长
(semi-major axis, semi-minor axis); angle:椭圆绕中心逆时针旋转的角度;startAngle/endAngle:椭圆弧的起止角,从长轴方向开始顺时针测量,0到360即完整椭圆。
下面在图像中心画一个半椭圆(白色、填充):
cv.ellipse(img, (256, 256), (100, 50), 0, 0, 180, 255, -1)
参数解读:中心 (256,256),半轴 (100,50),旋转角 0,从 0° 画到 180° 即半个椭圆,颜色 255(白色),-1 填充。
从 ellipse() 源码 看,angle、startAngle、endAngle 三个 double 入参会被 cvRound 取整为整数角度后传入 EllipseEx(),因此在 C++ 层面角度精度只到 1°;axes 会做非负断言 CV_Assert( axes.width >= 0 && axes.height >= 0 ... )。另有一个接受 RotatedRect 的重载(L1979 起),方便直接用检测器输出的旋转框画椭圆。
注意:
cv.ellipse()中使用的角度并不是常见的标准圆角度,其旋转方向与测量基准与其他 API 不同,使用angle参数时务必以实际渲染结果为准、通过startAngle/endAngle组合调试。
六、绘制多边形:cv.polylines()
绘制多边形的第一步是准备顶点坐标数组:形状为 ROWS x 1 x 2,其中 ROWS 为顶点数,且数据类型必须是 int32。下面画一个黄色四顶点小多边形:
pts = np.array([[10,5],[20,30],[70,20],[50,10]], np.int32)
pts = pts.reshape((-1, 1, 2))
cv.polylines(img, [pts], True, (0, 255, 255))
参数解读:[pts] 是"多边形列表"(外层列表表示可以一次画多个多边形),True 表示闭合,(0,255,255) 为 BGR 黄色。两点补充(与教程 Note 一致):
- 若第三个参数
isClosed为False,得到的是依次连接所有点的折线,而非闭合图形; cv.polylines()支持一次传入多条折线:把所有折线的点数组组成列表一次性传入即可,每条折线独立绘制。相比对每条线单独调用cv.line(),这是绘制一组线更快、更省的方式。
从源码印证这两点:polylines() 核心实现 通过 for( int i = 0; i < ncontours; i++ ) 循环逐个轮廓调用 PolyLine(..., isClosed, ...),正是"逐条独立绘制"的批量加速来源;而 Python 绑定层 cv::polylines() 会识别 vector<vector<Point>> / vector<Mat> 这类"多轮廓"输入(manyContours 分支),自动拆分成指针数组传入底层,这就是外层需要 [pts] 列表的原因。同时该层会校验每个点数组是 2 维 CV_32S 向量(CV_Assert(p.checkVector(2, CV_32S) >= 0)),与教程"必须 int32"的要求完全对应。
七、在图像上添加文字:cv.putText()
在图像上写文字需要指定:
- 要写入的文本内容;
- 放置位置坐标,即文字起始的左下角;
- 字体类型(
cv.FONT_HERSHEY_SIMPLEX等 Hershey 字体族); - 字体缩放比例
fontScale(决定文字大小); - 以及颜色、
thickness、lineType等常规参数。为了更好看,推荐lineType = cv.LINE_AA。
下面在图像左下角用白色写出 OpenCV:
font = cv.FONT_HERSHEY_SIMPLEX
cv.putText(img, 'OpenCV', (10, 500), font, 4, (255, 255, 255), 2, cv.LINE_AA)
参数解读:(10,500) 是文字基线左下角,font 为 Hershey 单线字体,4 是缩放倍数,2 是笔画粗细,cv.LINE_AA 让文字边缘平滑。
从 modules/imgproc/src/drawing_text.cpp 的源码结构看,putText() 有两代实现:传统的 Hershey 字体绘制入口(putText() 接受 fontface 与 ttsize),以及较新的 FontRenderEngine 流水线(putText_()),后者支持 TrueType/OpenType 等 Unicode 字体渲染。Python 中 cv.putText() 对应的仍是 Hershey 字体接口,支持的字体面即教程提到的 cv.FONT_HERSHEY_* 系列常量。
八、完整示例与结果
把以上代码段合并成一份可运行的完整脚本(img 逐段累积绘制,顺序不可打乱):
import numpy as np
import cv2 as cv
# 创建黑色图像
img = np.zeros((512, 512, 3), np.uint8)
# 1. 粗细 5px 的蓝色对角线
cv.line(img, (0, 0), (511, 511), (255, 0, 0), 5)
# 2. 右上角绿色矩形
cv.rectangle(img, (384, 0), (510, 128), (0, 255, 0), 3)
# 3. 矩形内的实心红圆
cv.circle(img, (447, 63), 63, (0, 0, 255), -1)
# 4. 图像中心的半椭圆(白色填充)
cv.ellipse(img, (256, 256), (100, 50), 0, 0, 180, 255, -1)
# 5. 左上角黄色小多边形
pts = np.array([[10, 5], [20, 30], [70, 20], [50, 10]], np.int32)
pts = pts.reshape((-1, 1, 2))
cv.polylines(img, [pts], True, (0, 255, 255))
# 6. 左下角白色文字
font = cv.FONT_HERSHEY_SIMPLEX
cv.putText(img, 'OpenCV', (10, 500), font, 4, (255, 255, 255), 2, cv.LINE_AA)
# 显示结果
cv.imshow('Drawing Demo', img)
cv.waitKey(0)
cv.destroyAllWindows()
运行后的效果即教程配图所示:蓝色对角线、右上角绿色矩形内嵌实心红圆、中心白色半椭圆、左上角黄色多边形、左下角白色 "OpenCV" 文字(见文首结果图 drawing_result.jpg)。注意 cv.imshow 依赖 GUI 支持,无头环境可改用 cv.imwrite() 保存图像再查看。
九、练习与延伸阅读
教程给出的实践题目:尝试使用 OpenCV 的绘制函数绘制 OpenCV 的 Logo。建议组合使用 cv.circle()(-1 填充)、cv.ellipse()(startAngle/endAngle 控制弧段)与 cv.polylines(),能完整检验本文所有 API。
进一步深入可查阅仓库中的这些位置:
- 全部形状绘制实现:modules/imgproc/src/drawing.cpp(含
line、rectangle、circle、ellipse、polylines、fillConvexPoly、fillPoly、drawContours); - 文字绘制实现:modules/imgproc/src/drawing_text.cpp;
- 线型常量定义:modules/imgproc/include/opencv2/imgproc.hpp(
LINE_AA = 16); - 原始教程文档:doc/py_tutorials/py_gui/py_drawing_functions/py_drawing_functions.markdown。
最后提醒两条容易踩坑的适用前提:cv.LINE_AA 仅在 CV_8U 深度图像上真正生效(否则自动降级为 8 连通,见上文源码);cv.polylines() 的点数组必须是 np.int32 类型且形状符合 N x 1 x 2 的约定,否则绑定层的 checkVector(2, CV_32S) 校验会直接报错。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
