Python OpenCV 初学者指南 – wiki基地


Python OpenCV 初学者指南:从零开始探索图像与视频的世界

计算机视觉,听起来可能像是一个遥不可攀的高科技领域,但借助像 OpenCV 这样强大且易于使用的库,结合 Python 简洁的语法,即使是初学者也能快速上手,探索图像处理和视频分析的奇妙世界。

本指南将带领你从安装 OpenCV 开始,逐步学习加载、显示、保存图片和视频,进行基本的图像操作,理解颜色空间,并涉及一些基础的图像处理技术,为你构建计算机视觉应用的基石。

第一章:初识 OpenCV 与 Python

1.1 什么是 OpenCV?

OpenCV (Open Source Computer Vision Library) 是一个开源的计算机视觉和机器学习软件库。它包含了数百种计算机视觉算法,涵盖了从基础的图像读取、处理到高级的目标检测、人脸识别、姿态估计等诸多领域。OpenCV 跨平台(支持 Windows, Linux, macOS, Android, iOS 等),并且接口丰富,支持 C++, Python, Java, MATLAB 等多种编程语言。

1.2 为什么选择 Python + OpenCV?

  • 易学易用: Python 以其简洁明了的语法著称,非常适合初学者入门。结合 OpenCV,可以快速实现想法,无需关注复杂的内存管理和底层细节。
  • 强大的生态系统: Python 拥有庞大的社区和丰富的第三方库,如 NumPy(OpenCV 的图像数据结构就是基于 NumPy 数组)、Matplotlib(用于数据可视化)、SciPy(科学计算)等,这些库与 OpenCV 协同工作,能极大地提升开发效率。
  • 快速原型开发: Python 的动态特性使得快速迭代和原型开发变得轻而易举。

1.3 OpenCV 能做什么?(一些应用示例)

  • 图像基础操作: 读取、显示、保存、缩放、旋转、裁剪。
  • 图像处理: 颜色空间转换、滤波、边缘检测、阈值分割、形态学操作。
  • 特征检测与描述: SIFT, SURF, ORB 等。
  • 目标检测: Haar 分类器(用于人脸检测)、深度学习框架集成(如 YOLO, SSD)。
  • 目标跟踪: KCF, CSRT 等多种跟踪算法。
  • 立体视觉: 深度估计、三维重建。
  • 视频分析: 背景建模、运动检测、光流。
  • 机器学习: 内置一些经典的机器学习算法。

看到这些,是不是对 OpenCV 充满期待了?让我们开始搭建环境吧!

第二章:环境搭建

2.1 安装 Python

如果你还没有安装 Python,请访问 Python 官网 下载并安装最新版本。推荐安装 Python 3.6 或更高版本。安装时请确保勾选 “Add Python to PATH”(将 Python 添加到环境变量),这将使你在命令行中方便地使用 pythonpip 命令。

2.2 安装 OpenCV

安装 Python 后,打开你的命令行终端(Windows 是 Command Prompt 或 PowerShell,macOS/Linux 是 Terminal),使用 pip 包管理器安装 OpenCV。

对于大多数基本功能,你可以安装:

bash
pip install opencv-python

如果你需要一些额外的非免费模块(例如 SIFT, SURF 等,虽然现在 SIFT/SURF 已经在新版本中免费,但为了保险或将来需要其他 contrib 模块,可以使用这个),可以安装:

bash
pip install opencv-contrib-python

选择其中一个安装即可。安装过程可能需要一些时间,取决于你的网络速度。

2.3 验证安装

安装完成后,可以在 Python 交互式环境或脚本中验证是否安装成功。

打开命令行,输入 python 进入 Python 交互环境,然后输入:

python
import cv2
print(cv2.__version__)

如果输出了 OpenCV 的版本号(例如 4.5.3),说明安装成功。如果报错 ModuleNotFoundError: No module named 'cv2',则说明安装失败,需要检查安装步骤或查找错误信息。

第三章:图像的读取、显示与保存

计算机视觉的第一步通常是处理图像。OpenCV 提供了简单直观的函数来完成这些基本任务。

3.1 读取图像

使用 cv2.imread() 函数来读取图像。

“`python
import cv2

指定图像文件的路径

请将 ‘path/to/your/image.jpg’ 替换为你自己的图片文件路径

image_path = ‘test_image.jpg’ # 假设当前目录下有一个 test_image.jpg

读取图像

cv2.IMREAD_COLOR:读取彩色图像(忽略透明度),这是默认值

cv2.IMREAD_GRAYSCALE:读取灰度图像

cv2.IMREAD_UNCHANGED:读取包含 Alpha 通道的彩色图像

img = cv2.imread(image_path, cv2.IMREAD_COLOR)

检查图像是否成功读取

if img is None:
print(f”错误:无法读取图像文件 ‘{image_path}'”)
else:
print(“图像读取成功!”)
# 你可以在这里继续处理图像
“`

重要提示: cv2.imread() 返回的是一个 NumPy 数组。对于彩色图像,它是一个三维数组 (height, width, channels),通道顺序是 BGR (蓝、绿、红),而不是常见的 RGB。对于灰度图像,它是一个二维数组 (height, width)。如果文件不存在或损坏,cv2.imread() 将返回 None。因此,总是检查返回值是否为 None 是一个好习惯。

3.2 显示图像

使用 cv2.imshow() 函数在窗口中显示图像。

“`python
import cv2
import numpy as np # OpenCV 图像是 NumPy 数组

假设 img 变量已经通过 cv2.imread() 成功读取

如果没有实际图片,我们可以创建一个虚拟图像用于示例

img = cv2.imread(‘test_image.jpg’, cv2.IMREAD_COLOR)

if img is None:
print(“图像读取失败,创建一个虚拟图像用于示例”)
# 创建一个 400×600 像素的蓝色图像 (BGR: [255, 0, 0])
img = np.zeros((400, 600, 3), dtype=np.uint8)
img[:,:] = (255, 0, 0) # 设置为蓝色
cv2.putText(img, “Sample Image (Blue)”, (50, 200), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)

显示图像

第一个参数是窗口名称(字符串)

第二个参数是要显示的图像数据(NumPy 数组)

cv2.imshow(‘Displayed Image’, img)

等待按键

cv2.waitKey() 是一个键盘绑定函数。它的参数是以毫秒为单位的时间。

如果参数是 0,它会无限期地等待键盘输入。

如果指定了时间(例如 1000 毫秒),它会在该时间后自动关闭窗口。

它返回按下的键的 ASCII 码,如果没有按键或者时间到,则返回 -1。

print(“按任意键关闭窗口…”)
cv2.waitKey(0)

销毁所有由 OpenCV 创建的窗口

cv2.destroyAllWindows()

print(“窗口已关闭。”)
“`

解释:

  • cv2.imshow() 创建一个窗口并显示图像。你需要给窗口指定一个名字。
  • cv2.waitKey(0) 是非常重要的。它让程序暂停,等待用户按下键盘上的任意一个键。如果没有这个函数,图像窗口会一闪而过(因为程序会立即执行到 destroyAllWindows 并退出)。参数为 0 表示无限等待,直到按键。如果参数大于 0,例如 waitKey(100),则表示等待 100 毫秒,如果在这 100 毫秒内没有按键,则继续执行后面的代码。这在处理视频或实时摄像头流时非常有用。
  • cv2.destroyAllWindows() 销毁所有 OpenCV 创建的窗口。如果你只创建了一个窗口,也可以使用 cv2.destroyWindow('Window Name') 来销毁指定的窗口。

3.3 保存图像

使用 cv2.imwrite() 函数将图像保存到文件。

“`python
import cv2
import numpy as np

假设 img 变量已经通过 cv2.imread() 或其他方式生成

我们还是创建一个虚拟图像用于示例

img = np.zeros((300, 500, 3), dtype=np.uint8)
img[:] = (0, 255, 0) # 设置为绿色
cv2.putText(img, “Saving Sample”, (50, 150), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2)

指定保存路径和文件名

文件扩展名决定了图像的格式(.jpg, .png, .bmp 等)

output_path = ‘saved_image.png’

保存图像

第一个参数是输出文件路径

第二个参数是要保存的图像数据

success = cv2.imwrite(output_path, img)

if success:
print(f”图像成功保存到 ‘{output_path}'”)
else:
print(f”错误:无法保存图像到 ‘{output_path}'”)

可以选择显示一下保存的图像来验证

saved_img = cv2.imread(output_path)

if saved_img is not None:

cv2.imshow(‘Saved Image Preview’, saved_img)

cv2.waitKey(0)

cv2.destroyAllWindows()

“`

cv2.imwrite() 的返回值是一个布尔值,表示保存操作是否成功。它会根据文件扩展名自动选择编码格式。

第四章:图像基本属性与像素访问

图像在 OpenCV 中被视为 NumPy 数组,这意味着你可以利用 NumPy 的强大功能来操作图像。

4.1 获取图像属性

你可以像访问 NumPy 数组一样获取图像的各种属性:

“`python
import cv2

img = cv2.imread(‘test_image.jpg’) # 确保文件存在

if img is not None:
# 形状 (height, width, channels)
# 对于灰度图,形状是 (height, width)
print(f”图像形状 (高, 宽, 通道): {img.shape}”)

# 像素总数 (高 * 宽 * 通道数)
print(f"图像像素总数: {img.size}")

# 图像数据类型 (通常是 uint8)
print(f"图像数据类型: {img.dtype}")

else:
print(“图像读取失败。”)
“`

  • img.shape: 返回一个元组,对于彩色图像是 (高度, 宽度, 通道数),对于灰度图像是 (高度, 宽度)
  • img.size: 返回图像中像素的总数,即 高度 * 宽度 * 通道数
  • img.dtype: 返回图像中像素的数据类型,通常是 uint8 (无符号 8 位整数),意味着每个像素的每个通道的取值范围是 0 到 255。

4.2 访问和修改像素

你可以直接通过像素的坐标来访问或修改其值。记住,坐标是 [行, 列][y, x],对于彩色图像还需要加上通道索引。OpenCV 的通道顺序是 BGR。

“`python
import cv2
import numpy as np

img = cv2.imread(‘test_image.jpg’)

if img is not None:
# 访问图像左上角 (0, 0) 的像素值
# 对于彩色图像,返回一个包含 B, G, R 值的列表/NumPy 数组
pixel_0_0 = img[0, 0]
print(f”左上角像素值 (BGR): {pixel_0_0}”)

# 访问指定位置 (例如,行 50,列 100) 的像素值
pixel_50_100 = img[50, 100]
print(f"像素 (50, 100) 值 (BGR): {pixel_50_100}")

# 只访问指定位置的蓝色通道值
blue_at_50_100 = img[50, 100, 0] # 0 是蓝色通道的索引
print(f"像素 (50, 100) 蓝色通道值: {blue_at_50_100}")

# 修改指定位置的像素值
# 将像素 (50, 100) 修改为红色 (BGR: [0, 0, 255])
img[50, 100] = [0, 0, 255]
print(f"修改后像素 (50, 100) 值 (BGR): {img[50, 100]}")

# 修改一个像素块 (例如,从 (10, 10) 到 (50, 50) 的区域)
# 使用 NumPy 切片
# 格式是 img[y_start:y_end, x_start:x_end]
img[10:50, 10:50] = [255, 255, 0] # 将这块区域设置为青色

# 显示修改后的图像
cv2.imshow('Modified Image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

else:
print(“图像读取失败。”)
“`

注意: 直接访问和修改单个像素通常效率较低,特别是对于大型图像。使用 NumPy 的切片和向量化操作会更高效。

4.3 图像 ROI (Region of Interest)

ROI 指的是图像中的特定区域。你可以使用 NumPy 切片来选择 ROI,并对其进行操作,就像操作任何其他 NumPy 数组一样。

“`python
import cv2

img = cv2.imread(‘test_image.jpg’)

if img is not None:
# 假设我们要提取图像中一个矩形区域作为 ROI
# 定义 ROI 的左上角坐标 (x, y) 和宽度 (w), 高度 (h)
x, y, w, h = 100, 50, 200, 150 # 示例坐标和尺寸

# 提取 ROI
# 切片格式:img[y_start : y_start + h, x_start : x_start + w]
roi = img[y : y + h, x : x + w]

# 现在你可以对 roi 进行独立操作,例如显示它
cv2.imshow('Original Image', img)
cv2.imshow('ROI', roi)

# 你也可以将 ROI 粘贴到图像的另一个位置 (需要确保尺寸匹配)
# 例如,将提取的 ROI 复制到图像的左上角
# 假设原始 ROI 尺寸是 150x200 (hxw)
# img[0:150, 0:200] = roi

cv2.waitKey(0)
cv2.destroyAllWindows()

else:
print(“图像读取失败。”)
“`

ROI 的概念在很多计算机视觉任务中都非常重要,比如只处理图像中包含特定对象的区域。

第五章:图像基本变换

对图像进行缩放、平移、旋转等几何变换是常见的操作。OpenCV 提供了相应的函数。

5.1 缩放 (Resizing)

使用 cv2.resize() 函数来改变图像的尺寸。

“`python
import cv2

img = cv2.imread(‘test_image.jpg’)

if img is not None:
# 获取原始图像尺寸
height, width = img.shape[:2]
print(f”原始尺寸: ({width}, {height})”)

# 方法一:按比例缩放
# 缩小到原来的一半
shrink_factor = 0.5
resized_shrink = cv2.resize(img, None, fx=shrink_factor, fy=shrink_factor, interpolation=cv2.INTER_AREA)
# cv2.INTER_AREA 通常用于缩小,效果较好

# 放大到原来的两倍
enlarge_factor = 2
resized_enlarge = cv2.resize(img, None, fx=enlarge_factor, fy=enlarge_factor, interpolation=cv2.INTER_CUBIC)
# cv2.INTER_CUBIC 或 cv2.INTER_LINEAR 通常用于放大

# 方法二:指定固定输出尺寸
# 注意:目标尺寸 (width, height) 是以 (宽度, 高度) 元组形式提供的
fixed_width, fixed_height = 300, 200
resized_fixed = cv2.resize(img, (fixed_width, fixed_height), interpolation=cv2.INTER_LINEAR)

print(f"缩小后尺寸: ({resized_shrink.shape[1]}, {resized_shrink.shape[0]})")
print(f"放大后尺寸: ({resized_enlarge.shape[1]}, {resized_enlarge.shape[0]})")
print(f"固定尺寸后尺寸: ({resized_fixed.shape[1]}, {resized_fixed.shape[0]})")


cv2.imshow('Original', img)
cv2.imshow('Resized (Shrink)', resized_shrink)
cv2.imshow('Resized (Enlarge)', resized_enlarge)
cv2.imshow('Resized (Fixed)', resized_fixed)

cv2.waitKey(0)
cv2.destroyAllWindows()

else:
print(“图像读取失败。”)
“`

cv2.resize() 的主要参数:

  • src: 输入图像。
  • dsize: 目标图像尺寸,以 (宽度, 高度) 元组表示。如果为 None,则通过 fxfy 计算。
  • fx: 沿水平方向的缩放因子。
  • fy: 沿垂直方向的缩放因子。
  • interpolation: 插值方法。不同的方法适用于不同的场景(缩小/放大)。

5.2 平移 (Translation)

平移是指将图像沿 x 和 y 方向移动。需要定义一个 2×3 的变换矩阵 M,然后使用 cv2.warpAffine() 函数。

变换矩阵 M 的形式如下:
$$
M = \begin{bmatrix} 1 & 0 & t_x \ 0 & 1 & t_y \end{bmatrix}
$$
其中 $t_x$ 是沿 x 方向的平移距离,$t_y$ 是沿 y 方向的平移距离。

“`python
import cv2
import numpy as np

img = cv2.imread(‘test_image.jpg’)

if img is not None:
height, width = img.shape[:2]

# 定义平移距离 (tx, ty)
# tx > 0 向右平移, tx < 0 向左平移
# ty > 0 向下平移, ty < 0 向上平移
tx, ty = 100, 50

# 创建 2x3 的平移矩阵 M
M = np.float32([[1, 0, tx], [0, 1, ty]])

# 进行仿射变换 (平移)
# src: 输入图像
# M: 变换矩阵
# dsize: 输出图像的尺寸 (宽度, 高度)
# warpAffine 会保留原始图像的尺寸,超出部分会被裁剪或填充
translated_img = cv2.warpAffine(img, M, (width, height))

cv2.imshow('Original', img)
cv2.imshow('Translated', translated_img)

cv2.waitKey(0)
cv2.destroyAllWindows()

else:
print(“图像读取失败。”)
“`

5.3 旋转 (Rotation)

旋转也需要一个变换矩阵,同样使用 cv2.warpAffine()。首先,需要使用 cv2.getRotationMatrix2D() 函数获取旋转矩阵。

“`python
import cv2
import numpy as np

img = cv2.imread(‘test_image.jpg’)

if img is not None:
height, width = img.shape[:2]

# 定义旋转中心 (通常是图像中心)
center = (width // 2, height // 2)

# 定义旋转角度 (以度为单位) 和缩放因子 (1.0 表示不缩放)
angle = 45 # 旋转 45 度
scale = 1.0 # 不缩放

# 获取旋转矩阵
# 第一个参数:旋转中心 (x, y)
# 第二个参数:旋转角度 (逆时针方向为正)
# 第三个参数:缩放因子
M = cv2.getRotationMatrix2D(center, angle, scale)

# 进行仿射变换 (旋转)
# 使用原始图像尺寸作为输出尺寸
rotated_img = cv2.warpAffine(img, M, (width, height))

# 注意:上述旋转会裁剪掉超出原始尺寸的部分
# 如果想保留整个旋转后的图像,需要计算新的边界尺寸
# 可以查找相关资料学习如何计算新的尺寸和调整变换矩阵

cv2.imshow('Original', img)
cv2.imshow('Rotated', rotated_img)

cv2.waitKey(0)
cv2.destroyAllWindows()

else:
print(“图像读取失败。”)
“`

第六章:图像绘制

在图像上绘制形状(线条、矩形、圆形)和文字是常见的需求,例如标记检测到的对象或添加注释。OpenCV 提供了易于使用的绘图函数。

注意: 绘图函数会直接修改输入的图像数据,如果你想保留原始图像,应该先创建一个副本 (img.copy()) 再进行绘制。

6.1 绘制线条

“`python
import cv2
import numpy as np

创建一个空白的黑色图像作为画布

500×500 像素,3 通道 (彩色),数据类型为 uint8

canvas = np.zeros((500, 500, 3), dtype=np.uint8)

绘制一条线条

第一个参数:图像(画布)

第二个参数:线条起点坐标 (x, y)

第三个参数:线条终点坐标 (x, y)

第四个参数:线条颜色 (BGR 格式)

第五个参数:线条粗细 (像素)

cv2.line(canvas, (50, 50), (450, 450), (0, 255, 0), 5) # 绿色线条

绘制另一条线条

cv2.line(canvas, (50, 450), (450, 50), (0, 0, 255), 3) # 红色线条

cv2.imshow(‘Drawing Lines’, canvas)
cv2.waitKey(0)
cv2.destroyAllWindows()
“`

6.2 绘制矩形

“`python
import cv2
import numpy as np

canvas = np.zeros((500, 500, 3), dtype=np.uint8)

绘制一个矩形

第一个参数:图像

第二个参数:矩形左上角坐标 (x, y)

第三个参数:矩形右下角坐标 (x, y)

第四个参数:颜色 (BGR)

第五个参数:线条粗细。如果是 -1,则矩形会被填充。

cv2.rectangle(canvas, (100, 100), (400, 400), (255, 0, 0), 2) # 蓝色边框

绘制一个填充的矩形

cv2.rectangle(canvas, (150, 150), (350, 350), (0, 255, 255), -1) # 黄色填充

cv2.imshow(‘Drawing Rectangles’, canvas)
cv2.waitKey(0)
cv2.destroyAllWindows()
“`

6.3 绘制圆形

“`python
import cv2
import numpy as np

canvas = np.zeros((500, 500, 3), dtype=np.uint8)

绘制一个圆形

第一个参数:图像

第二个参数:圆心坐标 (x, y)

第三个参数:半径

第四个参数:颜色 (BGR)

第五个参数:线条粗细 (-1 为填充)

cv2.circle(canvas, (250, 250), 150, (255, 0, 255), 3) # 紫色边框

绘制一个填充的圆形

cv2.circle(canvas, (250, 250), 70, (128, 128, 128), -1) # 灰色填充

cv2.imshow(‘Drawing Circles’, canvas)
cv2.waitKey(0)
cv2.destroyAllWindows()
“`

6.4 绘制文字

“`python
import cv2
import numpy as np

canvas = np.zeros((500, 500, 3), dtype=np.uint8)

绘制文字

第一个参数:图像

第二个参数:要写入的文本字符串

第三个参数:文本框的左下角坐标 (x, y)

第四个参数:字体类型(cv2.FONT_HERSHEY_SIMPLEX, cv2.FONT_HERSHEY_PLAIN 等)

第五个参数:字体缩放因子

第六个参数:颜色 (BGR)

第七个参数:线条粗细

第八个参数:线条类型 (可选,如 cv2.LINE_AA 使文字更平滑)

font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(canvas, ‘Hello OpenCV!’, (50, 100), font, 2, (255, 255, 255), 2, cv2.LINE_AA) # 白色文字

cv2.putText(canvas, ‘Beginners Guide’, (50, 200), cv2.FONT_HERSHEY_DUPLEX, 1.5, (0, 255, 255), 2) # 黄色文字

cv2.imshow(‘Drawing Text’, canvas)
cv2.waitKey(0)
cv2.destroyAllWindows()
“`

通过组合这些绘图函数,你可以在图像上创建各种视觉元素。

第七章:颜色空间转换

图像的颜色可以表示在不同的颜色空间中。除了前面提到的 BGR 和灰度,HSV (Hue, Saturation, Value/Brightness) 也是计算机视觉中非常重要的颜色空间,特别适用于基于颜色的对象检测。

使用 cv2.cvtColor() 函数进行颜色空间转换。

7.1 BGR 到灰度

将彩色图像转换为灰度图像是许多图像处理算法的第一步,因为灰度图像只需要处理一个通道的数据,计算量更小。

“`python
import cv2

img = cv2.imread(‘test_image.jpg’) # 确保文件存在且是彩色图

if img is not None:
# 将 BGR 图像转换为灰度图像
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

cv2.imshow('Original BGR Image', img)
cv2.imshow('Grayscale Image', gray_img)

print(f"原始图像形状: {img.shape}")
print(f"灰度图像形状: {gray_img.shape}") # 灰度图没有通道数维度

cv2.waitKey(0)
cv2.destroyAllWindows()

else:
print(“图像读取失败。”)
“`

7.2 BGR 到 HSV

HSV 颜色空间将颜色的感知属性分离:

  • H (Hue 色调): 描述颜色本身,如红色、黄色、蓝色等。取值范围通常是 [0, 179](在 OpenCV 中),对应 0-360 度。
  • S (Saturation 饱和度): 描述颜色的纯度或鲜艳程度。取值范围 [0, 255]。
  • V (Value/Brightness 明度/亮度): 描述颜色的亮度。取值范围 [0, 255]。

HSV 空间在处理光照变化时比 BGR 空间更稳定,常用于颜色分割。

“`python
import cv2

img = cv2.imread(‘test_image.jpg’) # 确保文件存在且是彩色图

if img is not None:
# 将 BGR 图像转换为 HSV 图像
hsv_img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

cv2.imshow('Original BGR Image', img)
cv2.imshow('HSV Image', hsv_img)

# 你也可以分别查看 H, S, V 通道 (它们是灰度图)
# h, s, v = cv2.split(hsv_img)
# cv2.imshow('Hue Channel', h)
# cv2.imshow('Saturation Channel', s)
# cv2.imshow('Value Channel', v)

cv2.waitKey(0)
cv2.destroyAllWindows()

else:
print(“图像读取失败。”)
“`

一个简单的 HSV 应用示例:颜色过滤

“`python
import cv2
import numpy as np

img = cv2.imread(‘color_test.jpg’) # 假设有一张包含不同颜色的图片

if img is not None:
# 将 BGR 转换为 HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# 定义要检测的颜色范围 (例如,蓝色)
# HSV 中蓝色的 H 值大致在 [100, 124] 范围内,S, V 范围用于过滤背景干扰
# 需要根据实际情况调整这些值
lower_blue = np.array([100, 50, 50])
upper_blue = np.array([124, 255, 255])

# 根据颜色范围创建掩码 (mask)
# mask 中,在指定颜色范围内的像素值为 255,否则为 0
mask = cv2.inRange(hsv, lower_blue, upper_blue)

# 使用掩码提取原始图像中的目标颜色区域
# res = cv2.bitwise_and(img, img, mask=mask)
# 或者,将非蓝色区域变黑,蓝色区域保留原色
res = img.copy()
res[mask == 0] = [0, 0, 0] # 将掩码中为 0 的像素 (非蓝色) 设置为黑色

cv2.imshow('Original Image', img)
cv2.imshow('Mask (Blue)', mask) # 掩码是灰度图
cv2.imshow('Blue Filtered', res) # 过滤后的图像

cv2.waitKey(0)
cv2.destroyAllWindows()

else:
print(“图像读取失败。请确保存在 ‘color_test.jpg’ 并包含蓝色物体,或者创建一张彩色图片进行测试。”)
“`

这个例子展示了 HSV 空间如何方便地进行颜色分割。通过调整 lower_blueupper_blue 的值,你可以过滤出其他颜色。

第八章:基础图像处理

图像处理是对图像进行操作,以改进图像质量、提取有用信息或为后续的高级计算机视觉任务做准备。

8.1 图像阈值化 (Thresholding)

阈值化是将灰度图像转换为二值图像(只有黑色和白色)的过程。通常用于将前景对象与背景分离。

“`python
import cv2

img = cv2.imread(‘test_image.jpg’, cv2.IMREAD_GRAYSCALE) # 读取灰度图

if img is not None:
# 应用简单阈值
# 第一个参数:输入的灰度图像
# 第二个参数:阈值 (小于该值的像素设为 0 或最大值,取决于阈值类型)
# 第三个参数:最大值 (高于阈值的像素设为该值)
# 第四个参数:阈值类型 (如 cv2.THRESH_BINARY, cv2.THRESH_BINARY_INV 等)

# cv2.THRESH_BINARY: pixel > thresh ? max_value : 0
ret, thresh1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)

# cv2.THRESH_BINARY_INV: pixel > thresh ? 0 : max_value
ret, thresh2 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)

# cv2.THRESH_TRUNC: pixel > thresh ? thresh : pixel
ret, thresh3 = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)

# cv2.THRESH_TOZERO: pixel > thresh ? pixel : 0
ret, thresh4 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)

# cv2.THRESH_TOZERO_INV: pixel > thresh ? 0 : pixel
ret, thresh5 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)

# threshold 函数返回两个值:ret (使用的阈值,特别是使用 Otsu 或 Triangle 方法时有用) 和 thresholded_image

cv2.imshow('Original Grayscale', img)
cv2.imshow('Binary Threshold', thresh1)
cv2.imshow('Binary Inverse', thresh2)
cv2.imshow('Truncate', thresh3)
cv2.imshow('To Zero', thresh4)
cv2.imshow('To Zero Inverse', thresh5)

cv2.waitKey(0)
cv2.destroyAllWindows()

else:
print(“图像读取失败,请确保存在 ‘test_image.jpg’ 并是图像文件。”)
“`

简单阈值化依赖于一个固定的阈值。如果图像光照不均匀,可能需要自适应阈值化 (cv2.adaptiveThreshold) 或 Otsu’s 二值化 (cv2.threshold(..., cv2.THRESH_OTSU))。

8.2 图像平滑 (Smoothing / Blurring)

图像平滑是用于减少图像噪声或细节的过程。常见的平滑方法包括均值滤波、高斯滤波、中值滤波等。高斯滤波是最常用的方法之一。

“`python
import cv2

img = cv2.imread(‘noisy_image.jpg’) # 假设有一张包含噪声的图片

if img is not None:
# 应用高斯模糊
# 第一个参数:输入图像
# 第二个参数:高斯核的大小 (宽度, 高度)。核的宽度和高度必须是奇数且可以不同,但建议相同。
# 第三个参数:沿 X 方向的标准差。如果只指定了 X 方向的标准差,Y 方向的标准差会取相同的值。如果都设为 0,则根据核的大小自动计算。
blurred_img = cv2.GaussianBlur(img, (5, 5), 0) # 使用 5×5 的高斯核

# 也可以尝试其他核大小,例如 9x9
# more_blurred_img = cv2.GaussianBlur(img, (9, 9), 0)

cv2.imshow('Original', img)
cv2.imshow('Blurred (Gaussian 5x5)', blurred_img)
# cv2.imshow('More Blurred (Gaussian 9x9)', more_blurred_img)

cv2.waitKey(0)
cv2.destroyAllWindows()

else:
print(“图像读取失败,请确保存在 ‘noisy_image.jpg’ 并包含噪声。”)
“`

高斯模糊的核大小越大,图像越模糊,噪声去除效果越明显,但图像细节损失也越多。

8.3 边缘检测 (Edge Detection)

边缘是图像中像素值发生显著变化的区域,它们通常对应于物体的边界。Canny 边缘检测是一种非常流行的边缘检测算法。

“`python
import cv2

img = cv2.imread(‘test_image.jpg’, cv2.IMREAD_GRAYSCALE) # Canny 通常在灰度图上执行

if img is not None:
# 应用 Canny 边缘检测
# 第一个参数:输入的灰度图像
# 第二个参数:低阈值 (用于滞后阈值法)
# 第三个参数:高阈值 (用于滞后阈值法)
# 建议高阈值是低阈值的 2 到 3 倍
edges = cv2.Canny(img, 100, 200)

# 可以先进行模糊,有助于减少噪声对边缘检测的影响
# blurred_img = cv2.GaussianBlur(img, (3, 3), 0)
# edges = cv2.Canny(blurred_img, 100, 200)


cv2.imshow('Original Grayscale', img)
cv2.imshow('Canny Edges', edges)

cv2.waitKey(0)
cv2.destroyAllWindows()

else:
print(“图像读取失败。”)
“`

Canny 算法涉及多个步骤(高斯模糊、计算梯度、非极大值抑制、滞后阈值),但 cv2.Canny() 函数将其封装起来,使用非常方便。调整两个阈值是控制边缘检测结果的关键。

第九章:视频处理基础

OpenCV 不仅可以处理静态图像,还能方便地处理视频流,无论是读取本地视频文件还是捕获摄像头输入。

9.1 读取和播放视频

视频可以看作是一系列连续的图像帧。处理视频的基本流程是:打开视频流,循环读取每一帧,对每一帧进行处理,然后显示或保存帧。

“`python
import cv2

创建 VideoCapture 对象

参数可以是视频文件路径 (字符串),或者是摄像头索引 (整数)。

通常 0 表示默认摄像头,1 表示第二个摄像头,以此类推。

cap = cv2.VideoCapture(‘test_video.mp4’) # 替换为你的视频文件路径,或使用 0 来打开摄像头

检查 VideoCapture 是否成功打开

if not cap.isOpened():
print(“错误:无法打开视频文件或摄像头。”)
exit()

print(“成功打开视频流,按 ‘q’ 键退出。”)

循环读取视频帧

while True:
# cap.read() 读取一帧。它返回两个值:
# ret: 一个布尔值,如果帧被成功读取,则为 True,否则为 False。
# frame: 读取到的图像帧 (一个 NumPy 数组)。
ret, frame = cap.read()

# 如果帧读取失败 (例如,视频结束或摄像头断开),则退出循环
if not ret:
    print("无法读取帧或视频已结束。")
    break

# 在这里可以对每一帧进行图像处理
# 例如,将帧转换为灰度:
# gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

# 显示当前帧
# 可以选择显示原始帧或处理后的帧
cv2.imshow('Video Frame', frame)
# cv2.imshow('Gray Frame', gray_frame)

# 检测键盘输入,按下 'q' 键时退出循环
# cv2.waitKey() 的参数是等待按键的毫秒数。对于视频,通常设置为一个很小的值 (例如 1),
# 这样可以流畅地播放视频,并且仍然可以检测到按键。
# 0xFF == ord('q') 是用来检测是否按下了 'q' 键的常用写法。
if cv2.waitKey(1) & 0xFF == ord('q'):
    break

释放 VideoCapture 对象和销毁所有窗口

cap.release()
cv2.destroyAllWindows()

print(“视频播放结束。”)
“`

9.2 保存视频

保存处理后的视频帧也需要一个 VideoWriter 对象。

“`python
import cv2

cap = cv2.VideoCapture(‘test_video.mp4’) # 读取输入视频

if not cap.isOpened():
print(“错误:无法打开输入视频。”)
exit()

获取输入视频的帧宽度、帧高度和帧率

frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS) # 获取帧率

print(f”输入视频尺寸: {frame_width}x{frame_height}, 帧率: {fps}”)

定义输出视频的文件名和编码器

四字符代码 (FourCC) 用于指定视频编码器。

常见的 FourCC 代码:

‘XVID’ 或 ‘MJPG’:适用于 avi 格式

‘mp4v’:适用于 mp4 格式 (一些平台可能不支持)

‘avc1’:H.264 编码,适用于 mp4 格式

请根据你的操作系统和安装的编码器选择合适的 FourCC

fourcc = cv2.VideoWriter_fourcc(*’XVID’) # 使用 XVID 编码器

创建 VideoWriter 对象

第一个参数:输出视频文件名

第二个参数:FourCC 编码

第三个参数:帧率 (应与输入视频或期望的输出视频帧率一致)

第四个参数:帧尺寸 (宽度, 高度)

out = cv2.VideoWriter(‘output_video.avi’, fourcc, fps, (frame_width, frame_height))

print(“正在处理视频并保存到 ‘output_video.avi’,按 ‘q’ 键停止。”)

while cap.isOpened():
ret, frame = cap.read()
if not ret:
print(“视频处理完毕或无法读取帧。”)
break

# 在这里可以对 frame 进行处理,例如转换为灰度:
# processed_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 注意:如果输出视频是彩色 (3 通道),即使你处理成了灰度,也要确保写入的是 3 通道图像 (可以通过 cv2.cvtColor 转换回来或使用堆叠 np.stack)
# 对于灰度输出视频,需要指定 FourCC 和输出图像尺寸为灰度图尺寸

# 将处理后的帧写入输出视频文件
out.write(frame) # 写入原始帧作为示例

# 显示原始帧(可选)
cv2.imshow('Original Frame', frame)
# cv2.imshow('Processed Frame', processed_frame)

# 按 'q' 键退出
if cv2.waitKey(1) & 0xFF == ord('q'):
    break

释放所有资源

cap.release()
out.release()
cv2.destroyAllWindows()

print(“视频保存完成。”)
“`

注意: 选择正确的 FourCC 编码和文件扩展名很重要。不是所有的 FourCC 编码都兼容所有的文件格式或在所有系统上都可用。如果保存失败,很可能是 FourCC 或扩展名的问题,可以尝试不同的组合。

第十章:进阶之路(简单提及)

一旦掌握了上述基础知识,你就可以开始探索 OpenCV 更强大的功能了:

  • 轮廓 (Contours): 查找并分析图像中连续的曲线,常用于形状分析和对象识别。cv2.findContours(), cv2.drawContours().
  • 直方图 (Histograms): 统计图像中像素值的分布,用于图像增强、比较、阈值化等。cv2.calcHist(), cv2.equalizeHist().
  • 特征检测与描述 (Feature Detection and Description): 识别图像中的关键点(如角点、斑点)并生成描述符,用于图像匹配、对象识别等。ORB 是一个不错的起点 (cv2.ORB_create()).
  • 对象检测 (Object Detection): 使用预训练的模型(如 Haar 分类器用于人脸、眼睛检测)或深度学习框架(如 YOLO, SSD)来检测图像中的特定对象。OpenCV 的 dnn 模块支持加载和运行多种深度学习模型。
  • 目标跟踪 (Object Tracking): 在视频序列中跟踪特定对象。OpenCV 提供了多种跟踪器。
  • 机器学习模块 (ml module): 包含一些传统的机器学习算法,如 K-Means 聚类、SVM 等。

这些主题都需要更深入的学习和实践,但你现在已经有了坚实的基础。

第十一章:学习资源推荐

  • OpenCV 官方文档: 这是最权威的资料,虽然有时对初学者来说可能过于详细,但它是查找特定函数用法和参数的最佳来源。https://docs.opencv.org/ (选择你安装的 OpenCV 版本对应的文档)
  • OpenCV-Python Tutorials: 官方文档中专门为 Python 用户编写的教程,涵盖了许多基础到进阶的主题。https://docs.opencv.org/latest/d6/d00/tutorial_py_root.html
  • 在线课程和书籍: Coursera, Udemy, YouTube 上有许多关于 Python 和 OpenCV 的课程。也有很多优秀的入门或进阶书籍可供参考。
  • 社区: Stack Overflow 是解决编程问题的宝库。OpenCV 论坛或相关的技术社区也能提供帮助。

第十二章:总结与展望

恭喜你!你已经走过了 Python OpenCV 初学者的重要旅程。你学会了如何:

  • 安装和验证 OpenCV。
  • 读取、显示和保存图像。
  • 理解图像的基本属性和像素访问。
  • 进行图像的缩放、平移和旋转。
  • 在图像上绘制形状和文字。
  • 理解并应用颜色空间转换。
  • 进行基础图像处理,如阈值化、模糊和边缘检测。
  • 读取和播放视频,以及保存视频。

这只是计算机视觉领域的冰山一角,但你已经具备了进一步探索所需的工具和知识。最重要的是多动手实践,尝试用 OpenCV 解决一些小问题,比如:

  • 写一个脚本,批量将一个文件夹下的图片转换为灰度图。
  • 在摄像头画面中实时显示当前的帧率。
  • 尝试在摄像头画面中用一个矩形框标记出某个特定颜色的物体(结合 HSV 颜色过滤)。
  • 读取一个视频,然后将其旋转 90 度后保存为新的视频。

计算机视觉是一个充满挑战和乐趣的领域,广泛应用于人工智能、机器人、医疗、安全等各个方面。希望这篇指南能够点燃你对计算机视觉的兴趣,并成为你继续深入学习的良好开端。

祝你在计算机视觉的探索之路上一切顺利!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部