首页
/ OpenCV Python 绘图函数实战:cv.line、cv.rectangle、cv.circle、cv.ellipse 与 cv.putText 全解

OpenCV Python 绘图函数实战:cv.line、cv.rectangle、cv.circle、cv.ellipse 与 cv.putText 全解

2026-09-06 12:36:12作者:伍希望

本篇基于 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 Python 绘图函数最终效果:包含蓝色对角线、绿色矩形、红色填充圆、半椭圆、黄色多边形与 OpenCV 文字

一、绘图函数的公共参数

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:椭圆弧的起止角,从长轴方向开始顺时针测量,0360 即完整椭圆。

下面在图像中心画一个半椭圆(白色、填充):

cv.ellipse(img, (256, 256), (100, 50), 0, 0, 180, 255, -1)

参数解读:中心 (256,256),半轴 (100,50),旋转角 0,从 画到 180° 即半个椭圆,颜色 255(白色),-1 填充。

ellipse() 源码 看,anglestartAngleendAngle 三个 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 一致):

  • 若第三个参数 isClosedFalse,得到的是依次连接所有点的折线,而非闭合图形;
  • 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(决定文字大小);
  • 以及颜色、thicknesslineType 等常规参数。为了更好看,推荐 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() 接受 fontfacettsize),以及较新的 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。

进一步深入可查阅仓库中的这些位置:

最后提醒两条容易踩坑的适用前提:cv.LINE_AA 仅在 CV_8U 深度图像上真正生效(否则自动降级为 8 连通,见上文源码);cv.polylines() 的点数组必须是 np.int32 类型且形状符合 N x 1 x 2 的约定,否则绑定层的 checkVector(2, CV_32S) 校验会直接报错。

登录后查看全文
热门项目推荐
相关项目推荐