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 添加到环境变量),这将使你在命令行中方便地使用 python
和 pip
命令。
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
,则通过fx
和fy
计算。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_blue
和 upper_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 度后保存为新的视频。
计算机视觉是一个充满挑战和乐趣的领域,广泛应用于人工智能、机器人、医疗、安全等各个方面。希望这篇指南能够点燃你对计算机视觉的兴趣,并成为你继续深入学习的良好开端。
祝你在计算机视觉的探索之路上一切顺利!