OpenCV OpenCV Python 轮廓与矩阵实战:NumPy 矩阵操作 + findContours 详解
引言
在 OpenCV Python 中,图像的本质就是 NumPy 多维数组(也称为矩阵或张量)。无论是简单的像素赋值、通道分离,还是复杂的轮廓检测与形状分析,所有操作的底层都是对矩阵的运算。因此,在深入学习轮廓检测之前,掌握 NumPy 矩阵的基本操作是必不可少的。本文将从矩阵基础出发,逐步过渡到 cv2.findContours 的完整实战,帮助你建立从像素到轮廓的完整认知链路。
第一部分:NumPy 图像矩阵操作
本示例使用的 OpenCV 版本是:4.1.1,运行环境为 Jupyter Notebook 6.0.0。
1. 加载依赖库
import cv2
import numpy as np
import matplotlib.pyplot as plt
2. 矩阵的核心概念
图像矩阵有两个关键特征——形状(shape)和数据类型(dtype):
- shape:矩阵的维度信息。例如一个 480×640 的 3 通道图像,其
shape为(480, 640, 3),分别表示高度、宽度和通道数。 - dtype:矩阵中每个元素的数据类型。
np.uint8表示每个元素占 8 位无符号整数,取值范围为 0~255。
3. 使用 np.full 创建矩阵
# 创建一个 480x640 的 3 通道矩阵,用 255 填充——得到一张纯白色图像
image = np.full((480, 640, 3), 255, np.uint8)
plt.figure(figsize=(9, 9))
plt.imshow(image)

注意:
figsize以英寸为单位定义图像窗口大小(1 英寸 = 2.54 厘米),设置较大的值可以让图像显示得更清晰。
4. 指定通道值创建彩色图像
# 第三通道设为 255,在 BGR 模式下代表红色
image = np.full((480, 640, 3), (0, 0, 255), np.uint8)
# matplotlib 使用 RGB 色彩空间,需要先做 BGR→RGB 转换
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(9, 9))
plt.imshow(image)

5. 使用 fill 填充矩阵
# 用 0 填充整个矩阵——得到一张纯黑色图像
image.fill(0)
plt.figure(figsize=(9, 9))
plt.imshow(image)

6. 指定元素赋值——单点操作
# 将三个指定坐标的像素设为白色
image[240, 160] = image[240, 320] = image[240, 480] = (255, 255, 255)
plt.figure(figsize=(9, 9))
plt.imshow(image)

仔细查看图片,可以看到 3 个白色像素点,恰好位于 [240, 160]、[240, 320]、[240, 480] 这三个坐标位置。
7. 指定元素赋值——整通道操作
# 将第一个通道全部设为 255(在 RGB 模式下显示为红色)
image[:, :, 0] = 255
plt.figure(figsize=(9, 9))
plt.imshow(image)

注意:仔细观察仍然可以看到之前设置的 3 个白色像素点,因为它们的三个通道都已经是 255。
8. 指定元素赋值——垂直线操作
# 将图像中间垂直线上所有像素设为白色
image[:, 320, :] = 255
plt.figure(figsize=(9, 9))
plt.imshow(image)

9. 指定元素赋值——区域 + 通道操作
# 在区域 [100:600, 100:200] 内,将通道索引 2 的值设为 255
# 通道索引 2 在 RGB 中是蓝色通道,红色 + 蓝色 = 紫红色
image[100:600, 100:200, 2] = 255
plt.figure(figsize=(9, 9))
plt.imshow(image)

矩阵操作小结
| 操作 | 语法 | 说明 |
|---|---|---|
| 访问元素 | image[240, 160] | 返回该像素的三通道值数组 |
| 访问单通道 | image[240, 160, 1] | 返回该像素第二通道的值 |
| 全行/列选择 | image[:, 160] | y 为 160 的所有像素 |
| 区域选择 | image[120:140, 160] | x 为 120~140,y 为 160 的区域 |
| 整通道赋值 | image[:, :, 0] = 255 | 第一个通道全部设为 255 |
NumPy 支持高维数组与矩阵运算,也提供了大量数学函数库。在使用 PyTorch 等深度学习框架时,NumPy 数组还可以非常方便地转换成张量交给 GPU 处理。
第二部分:轮廓检测 findContours
什么是轮廓?
轮廓可以简单地解释为连接具有相同颜色或强度的所有连续点(沿边界)的曲线。轮廓是用于形状分析以及对象检测和识别的有用工具。
为了获得更高的准确性,使用二进制图像效果会更佳。因此,在找到轮廓之前,请先使用阈值处理或 Canny 边缘检测。自从 OpenCV 3.2,findContours() 不再修改原图片,因此不需要事先复制一份。
在 OpenCV 中,找到轮廓就像从黑色背景中找到白色物体——要找到的对象应该是白色,背景应该是黑色。
基本用法
import numpy as np
import cv2 as cv
im = cv.imread('test.jpg')
imgray = cv.cvtColor(im, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
程序先读入 test.jpg,转成灰度图,以 127 作为阈值进行二值化处理,再执行轮廓查找。
cv.findContours() 有三个参数:
- 源图像:输入的二值图像。
- 轮廓检索模式:
RETR_EXTERNAL— 仅检索最外部轮廓RETR_LIST— 检索所有轮廓而不建立任何层次关系RETR_CCOMP— 检索所有轮廓并将它们组织成两级层次结构。顶层是组件的外部边界,第二层是孔的边界RETR_TREE— 检索所有轮廓并重建嵌套轮廓的完整层次结构RETR_FLOODFILL— 多级图像的连通分量
- 轮廓逼近方法(详见下文)。
返回值 contours 是一个 Python 列表,每个元素是对象边界点的 (x, y) 坐标的 NumPy 数组。hierarchy 描述轮廓之间的嵌套关系。
绘制轮廓
使用 cv.drawContours() 函数绘制轮廓:
# 绘制所有轮廓
cv.drawContours(img, contours, -1, (0, 255, 0), 3)
# 绘制第 4 个轮廓
cv.drawContours(img, contours, 3, (0, 255, 0), 3)
# 更常用的方式:先取出轮廓再绘制
cnt = contours[4]
cv.drawContours(img, [cnt], 0, (0, 255, 0), 3)
轮廓逼近方法
这是 cv.findContours 的第三个参数,决定轮廓是否存储所有边界点:
cv.CHAIN_APPROX_NONE:存储所有边界点。cv.CHAIN_APPROX_SIMPLE:删除冗余点并压缩轮廓,节省内存。例如一条直线只需保留两个端点。
下面的矩形图像直观地演示了差异:CHAIN_APPROX_NONE 得到 734 个点,而 CHAIN_APPROX_SIMPLE 只得到 4 个点——内存节省效果非常显著!

不同阈值的实战对比
我们以一幅包含多种形状的图像为例,对比不同阈值参数下的轮廓检测效果。
原图:

参数:阈值=127,轮廓检索模式=RETR_TREE,轮廓逼近方法=CHAIN_APPROX_SIMPLE
| 原图 | 灰度处理后 | 阈值处理后 | 画出轮廓 |
|---|---|---|---|
![]() | ![]() | ![]() | ![]() |
从处理结果可以看到,因为阈值过低,导致偏暖色的形状被过滤,无法正确获取所有轮廓。
参数:阈值=200,轮廓检索模式=RETR_TREE,轮廓逼近方法=CHAIN_APPROX_SIMPLE
| 原图 | 灰度处理后 | 阈值处理后 | 画出轮廓 |
|---|---|---|---|
![]() | ![]() | ![]() | ![]() |
虽然这次把所有轮廓都画出来了,但也发现了另一个问题:整幅图的边缘和某些形状的内圈也被画上了轮廓。

至于这些轮廓之间的顺序关系,我们将在后续文章中详细讨论。
第三部分:矩阵 + 轮廓综合实战
理解了矩阵操作和轮廓检测之后,我们可以将两者结合起来:用 NumPy 创建图像矩阵并绘制形状,然后用 findContours 检测这些形状的轮廓。
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
# 1. 用 NumPy 创建一张黑色背景图像
image = np.full((480, 640, 3), 0, np.uint8)
# 2. 在矩阵上绘制白色形状
cv.rectangle(image, (50, 50), (200, 200), (255, 255, 255), -1) # 白色矩形
cv.circle(image, (400, 240), 100, (255, 255, 255), -1) # 白色圆形
cv.line(image, (250, 400), (550, 400), (255, 255, 255), 3) # 白色线段
# 3. 转灰度 + 阈值处理
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(gray, 127, 255, 0)
# 4. 查找并绘制轮廓
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
cv.drawContours(image, contours, -1, (0, 255, 0), 2)
# 5. 显示结果
plt.figure(figsize=(9, 9))
plt.imshow(cv2.cvtColor(image, cv.COLOR_BGR2RGB))
plt.title(f'Detected {len(contours)} contours')
plt.show()
这段代码完整展示了工作流:创建矩阵 → 绘制形状 → 灰度转换 → 阈值处理 → 轮廓检测 → 结果可视化。通过矩阵操作,你可以精确控制图像中的每一个像素,而轮廓检测则让你从像素级别上升到形状级别的分析和理解。
总结
矩阵操作是 OpenCV 图像处理的基石——从创建空白画布到精确的像素级编辑,NumPy 提供了强大而灵活的工具链。轮廓检测则是形状分析和目标识别的核心入口,cv2.findContours 通过不同的检索模式和逼近方法,可以适应从简单几何体到复杂嵌套结构的各类场景。掌握了这两部分知识,你就拥有了从底层像素到高层语义的完整图像处理能力。





