|
OpenCV Python 轮廓与矩阵实战:NumPy 矩阵操作 + findContours 详解

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() 有三个参数:

  1. 源图像:输入的二值图像。
  2. 轮廓检索模式
    • RETR_EXTERNAL — 仅检索最外部轮廓
    • RETR_LIST — 检索所有轮廓而不建立任何层次关系
    • RETR_CCOMP — 检索所有轮廓并将它们组织成两级层次结构。顶层是组件的外部边界,第二层是孔的边界
    • RETR_TREE — 检索所有轮廓并重建嵌套轮廓的完整层次结构
    • RETR_FLOODFILL — 多级图像的连通分量
  3. 轮廓逼近方法(详见下文)。

返回值 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 通过不同的检索模式和逼近方法,可以适应从简单几何体到复杂嵌套结构的各类场景。掌握了这两部分知识,你就拥有了从底层像素到高层语义的完整图像处理能力。