OpenCV Python 初学者教程:开启你的计算机视觉之旅
计算机视觉(Computer Vision)是一门迷人的学科,它赋予了计算机“看”和“理解”图像与视频的能力。从自动驾驶汽车到人脸识别解锁手机,再到医疗影像分析,计算机视觉技术无处不在。而OpenCV(Open Source Computer Vision Library)则是这个领域最强大、最受欢迎的开源库之一。结合Python语言简洁易学的特性,OpenCV Python成为了快速入门计算机视觉的最佳组合。
本篇文章将带你从零开始,逐步掌握OpenCV Python的基础知识和常用操作。无论你是否有图像处理经验,只要你具备基本的Python编程能力,本教程都将引领你踏上计算机视觉的奇妙旅程。
文章目录
- 引言:什么是OpenCV?为什么选择Python?
- 环境搭建:安装OpenCV Python
- 图像的基础操作:加载、显示、保存
- 图像属性与像素访问
- 色彩空间:BGR、灰度图与HSV
- 在图像上绘图:点、线、矩形、圆与文本
- 图像基本变换:缩放、平移、旋转与仿射变换
- 图像滤波:模糊与平滑
- 图像阈值化:二值化图像
- 边缘检测:Canny算法
- 轮廓检测与分析
- 视频处理:读取、显示与保存视频
- 综合案例:简单的图像处理应用
- 进阶之路:OpenCV的更多功能
- 总结与展望
1. 引言:什么是OpenCV?为什么选择Python?
什么是OpenCV?
OpenCV(Open Source Computer Vision Library)是一个跨平台的计算机视觉库,由Intel开发,现在由Willow Garage和Itseez维护。它包含了数百个计算机视觉、机器学习和图像处理的算法。OpenCV的设计目标是为实时应用提供计算效率,因此底层代码是用优化的C++编写的。然而,它提供了多种语言的接口,包括C++, Java, MATLAB, 和我们今天要重点学习的Python。
OpenCV涵盖了计算机视觉领域的诸多核心功能,包括但不限于:
- 图像和视频的读取、写入和处理
- 基本的图像处理操作(如滤波、形态学操作)
- 特征检测和描述
- 目标检测(如人脸检测、物体识别)
- 对象跟踪
- 三维重建
- 相机校准
- 机器学习算法(如SVM, K-Means)
为什么选择Python?
Python以其简洁的语法和强大的生态系统成为了近年来最受欢迎的编程语言之一。对于OpenCV来说,选择Python作为接口有以下几个显著优势:
- 易学易用: Python的语法非常直观,即使是编程初学者也能快速上手。
- 开发效率高: 相比C++等编译型语言,Python的开发周期更短,非常适合原型开发和实验。
- 丰富的库支持: Python拥有庞大的第三方库生态系统,特别是科学计算和数据处理领域,如NumPy、SciPy、Matplotlib等。OpenCV Python接口与NumPy数组紧密集成,使得图像处理操作变得异常便捷。
- 社区活跃: Python拥有庞大而活跃的社区,遇到问题时很容易找到解决方案和学习资源。
将OpenCV的强大功能与Python的易用性结合,我们可以高效地构建各种计算机视觉应用。
2. 环境搭建:安装OpenCV Python
开始之前,你需要确保你的系统上安装了Python。推荐使用Python 3.6或更高版本。安装OpenCV Python最简单的方法是使用pip包管理器。
在终端或命令提示符中运行以下命令:
bash
pip install opencv-python numpy
这个命令会安装opencv-python
库。为什么还要安装numpy
呢?因为OpenCV在处理图像时,内部是将图像存储为多维NumPy数组的。OpenCV的许多函数都接受NumPy数组作为输入,并返回NumPy数组作为输出。NumPy提供了高效的数组操作功能,与OpenCV的结合非常紧密且必要。
可选(用于更多贡献版功能):
如果你需要使用OpenCV的贡献版模块(例如一些最新的算法或非免费模块),可以安装opencv-contrib-python
:
bash
pip install opencv-contrib-python numpy
请注意,opencv-contrib-python
包含了标准版的所有功能,所以你通常只需要安装其中一个。对于初学者,opencv-python
通常就足够了。
验证安装:
安装完成后,你可以通过简单的Python脚本来验证OpenCV是否成功安装:
python
import cv2
print(cv2.__version__)
如果你能成功导入cv2
模块并打印出版本号,说明安装成功!
推荐使用虚拟环境:
为了避免不同项目之间的库版本冲突,强烈建议使用虚拟环境(如venv
或conda
)来管理你的Python项目依赖。
例如,使用venv
创建一个虚拟环境:
bash
python -m venv myenv
激活虚拟环境:
- Windows:
myenv\Scripts\activate
- macOS/Linux:
source myenv/bin/activate
激活环境后,再在虚拟环境中安装OpenCV和NumPy。
3. 图像的基础操作:加载、显示、保存
计算机视觉的首要任务通常是处理图像。OpenCV提供了简单易用的函数来加载、显示和保存图像文件。
3.1 加载图像
使用cv2.imread()
函数加载图像。
“`python
import cv2
import numpy as np # 虽然这里不用,但通常会一起导入
指定图像文件的路径
请将 ‘path/to/your/image.jpg’ 替换为你实际的图像文件路径
确保图像文件与你的Python脚本在同一目录下,或使用完整路径
image_path = ‘path/to/your/image.jpg’
使用cv2.imread()加载图像
参数1: 图像文件路径
参数2: 加载标志
cv2.IMREAD_COLOR: 加载彩色图像(默认)
cv2.IMREAD_GRAYSCALE: 加载灰度图像
cv2.IMREAD_UNCHANGED: 加载包含Alpha通道的图像
img_color = cv2.imread(image_path, cv2.IMREAD_COLOR)
img_gray = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
检查图像是否成功加载
if img_color is None:
print(f”错误:无法加载彩色图像文件 ‘{image_path}'”)
else:
print(“彩色图像加载成功!”)
if img_gray is None:
print(f”错误:无法加载灰度图像文件 ‘{image_path}'”)
else:
print(“灰度图像加载成功!”)
图像加载失败时,cv2.imread()会返回None
在后续操作中,你应该总是检查图像是否加载成功
“`
注意: cv2.imread()
返回的是一个NumPy数组。如果文件不存在或加载失败,它将返回None
。在进行任何图像处理之前,务必检查加载是否成功。
3.2 显示图像
使用cv2.imshow()
函数在窗口中显示图像。
“`python
假设 img_color 和 img_gray 已经成功加载
if img_color is not None:
# cv2.imshow() 用于创建一个窗口并显示图像
# 参数1: 窗口标题(字符串)
# 参数2: 要显示的图像(NumPy数组)
cv2.imshow(‘Color Image Window’, img_color)
print(“正在显示彩色图像…”)
if img_gray is not None:
cv2.imshow(‘Grayscale Image Window’, img_gray)
print(“正在显示灰度图像…”)
cv2.waitKey() 用于等待键盘事件
参数是等待的毫秒数。0表示无限等待,直到按下任意键
如果参数 > 0,它会在等待指定的毫秒数后自动销毁窗口
它返回按下的键的ASCII值,如果没有按键则返回-1
print(“按下任意键继续…”)
cv2.waitKey(0)
cv2.destroyAllWindows() 用于销毁所有OpenCV创建的窗口
cv2.destroyWindow(‘Window Name’) 用于销毁指定的窗口
cv2.destroyAllWindows()
print(“窗口已关闭。”)
“`
cv2.waitKey(0)
是一个非常重要的函数。它会暂停程序的执行,直到用户按下键盘上的一个键。如果没有这个函数,cv2.imshow()
显示的窗口会瞬间出现然后消失,因为程序会立即执行到结束。参数0
表示无限等待,直到用户按键。如果希望窗口在一定时间后自动关闭,可以设置一个大于0的数字(例如cv2.waitKey(5000)
会等待5秒)。
cv2.destroyAllWindows()
会关闭所有由OpenCV创建的窗口。
3.3 保存图像
使用cv2.imwrite()
函数将图像保存到文件。
“`python
假设 img_color 已经成功加载
if img_color is not None:
# cv2.imwrite() 用于保存图像
# 参数1: 保存文件的路径和文件名(包含扩展名,如.jpg, .png等)
# 参数2: 要保存的图像(NumPy数组)
output_path = ‘output_image_color.png’ # 可以指定不同的格式
success = cv2.imwrite(output_path, img_color)
if success:
print(f"彩色图像已成功保存到 '{output_path}'")
else:
print(f"保存图像到 '{output_path}' 失败!")
也可以保存灰度图像
if img_gray is not None:
output_path_gray = ‘output_image_gray.jpg’
success_gray = cv2.imwrite(output_path_gray, img_gray)
if success_gray:
print(f”灰度图像已成功保存到 ‘{output_path_gray}'”)
else:
print(f”保存图像到 ‘{output_path_gray}’ 失败!”)
“`
cv2.imwrite()
根据指定的文件扩展名来决定保存的格式。常见的格式有.jpg
, .png
, .bmp
, .tif
等。
小结:
加载、显示和保存是进行任何图像处理的基石。熟练掌握cv2.imread()
, cv2.imshow()
, cv2.waitKey()
, cv2.destroyAllWindows()
, 和cv2.imwrite()
是入门OpenCV的第一步。
4. 图像属性与像素访问
加载图像后,了解图像的属性以及如何访问和修改单个像素是非常重要的。由于图像在OpenCV中表示为NumPy数组,我们可以利用NumPy的强大功能来操作图像。
“`python
import cv2
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path)
if img is None:
print(f”错误:无法加载图像文件 ‘{image_path}'”)
else:
# 4.1 图像属性
# shape: 获取图像的形状 (高度, 宽度, 通道数)。灰度图只有 (高度, 宽度)。
print(f”图像形状 (高, 宽, 通道): {img.shape}”) # 例如: (400, 600, 3) 表示高400,宽600,3个通道(BGR)
# size: 获取图像总像素数 (高度 * 宽度 * 通道数)
print(f"图像总像素数: {img.size}") # 例如: 400 * 600 * 3
# dtype: 获取图像像素的数据类型
print(f"像素数据类型: {img.dtype}") # 例如: uint8 (无符号8位整数,表示0-255的像素值)
# 4.2 像素访问
# 访问单个像素的值 (对于彩色图像是 BGR 值)
# OpenCV使用 (行, 列) 或 (y, x) 的坐标顺序,与NumPy一致
# 注意:不是常见的 (x, y)
px = img[100, 200] # 访问第100行、第200列的像素
print(f"像素 (100, 200) 的值 (BGR): {px}") # 例如: [150 120 90]
# 访问彩色图像特定通道的像素值 (BGR)
# img[y, x, channel_index]
# channel_index 0: 蓝色通道 (B)
# channel_index 1: 绿色通道 (G)
# channel_index 2: 红色通道 (R)
blue_value = img[100, 200, 0]
green_value = img[100, 200, 1]
red_value = img[100, 200, 2]
print(f"像素 (100, 200) 的蓝色值: {blue_value}")
print(f"像素 (100, 200) 的绿色值: {green_value}")
print(f"像素 (100, 200) 的红色值: {red_value}")
# 如果是灰度图像,直接访问 img[y, x] 获取灰度值
# 假设 img_gray 已经加载成功
# gray_px = img_gray[100, 200]
# print(f"灰度像素 (100, 200) 的值: {gray_px}")
# 4.3 修改像素值
# 可以直接修改像素的值
img[100, 200] = [0, 0, 255] # 将像素 (100, 200) 设置为红色 (BGR: 0, 0, 255)
print(f"修改后像素 (100, 200) 的值 (BGR): {img[100, 200]}")
# 注意:直接修改像素效率较低,尤其是对大块区域。
# NumPy的数组切片和操作更高效,也是OpenCV函数的内部实现方式。
# 使用NumPy切片获取图像区域 (ROI - Region of Interest)
# roi = img[y_start:y_end, x_start:x_end]
roi = img[50:150, 150:250] # 获取从y=50到149,x=150到249的区域 (100x100像素)
print(f"ROI 区域的形状: {roi.shape}")
# 修改ROI区域 (例如将ROI设置为黑色)
img[50:150, 150:250] = [0, 0, 0] # 将该区域所有像素设置为黑色
# 显示修改后的图像 (你可以看到修改过的像素或区域)
cv2.imshow('Image with Modified Pixel and ROI', img)
print("显示修改后的图像...")
cv2.waitKey(0)
cv2.destroyAllWindows()
“`
重要提示:
- OpenCV加载的彩色图像默认是 BGR 顺序,而不是更常见的RGB。这意味着对于一个像素,
[B, G, R]
对应着蓝色、绿色和红色的分量值。这是OpenCV历史遗留的设计选择,初学者需要特别注意。 - 访问像素的坐标顺序是
(y, x)
或(行, 列)
,这与通常的笛卡尔坐标系(x, y)
是相反的。 - 图像像素通常是
uint8
(无符号8位整数)类型,值范围是0到255。进行图像运算时,如果结果超出这个范围,需要特别小心,OpenCV和NumPy在处理溢出时行为可能不同。对于一些需要中间结果超出255的运算,可以考虑将图像数据类型转换为更高的精度,如float32
。
5. 色彩空间:BGR、灰度图与HSV
除了默认的BGR彩色空间,OpenCV还支持多种色彩空间,如灰度图(Grayscale)、HSV(色相、饱和度、亮度)、HLS、YCrCb等。将图像转换到不同的色彩空间可以方便进行特定的图像处理任务。最常见的转换是彩色到灰度,以及到HSV。
5.1 BGR 到 灰度图
灰度图只包含一个通道,每个像素只有一个值表示亮度。将彩色图转换为灰度图可以减少数据量,同时保留图像的结构信息,这在许多算法中是首选的预处理步骤(如边缘检测、轮廓检测)。
“`python
import cv2
image_path = ‘path/to/your/image.jpg’
img_color = cv2.imread(image_path)
if img_color is not None:
# cv2.cvtColor() 用于转换色彩空间
# 参数1: 输入图像
# 参数2: 转换标志
# cv2.COLOR_BGR2GRAY: 将BGR图像转换为灰度图像
img_gray = cv2.cvtColor(img_color, cv2.COLOR_BGR2GRAY)
cv2.imshow('Original BGR Image', img_color)
cv2.imshow('Grayscale Image', img_gray)
print("正在显示原始彩色图和灰度图...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法加载图像文件 ‘{image_path}'”)
“`
5.2 BGR 到 HSV
HSV色彩空间由Hue(色相)、Saturation(饱和度)和Value(亮度)组成。它比BGR空间更能反映人对颜色的感知。在基于颜色识别或分割物体时,HSV空间常常更加方便。例如,要识别图像中的红色物体,在HSV空间中,红色在一个特定的H(色相)范围内,而S(饱和度)和V(亮度)可以用来区分鲜艳的红色和暗淡的红色,这比在BGR空间中通过三个通道的组合来定义颜色要容易得多。
“`python
import cv2
image_path = ‘path/to/your/image.jpg’
img_color = cv2.imread(image_path)
if img_color is not None:
# cv2.COLOR_BGR2HSV: 将BGR图像转换为HSV图像
img_hsv = cv2.cvtColor(img_color, cv2.COLOR_BGR2HSV)
cv2.imshow('Original BGR Image', img_color)
cv2.imshow('HSV Image', img_hsv) # HSV图像直接显示看起来可能有点奇怪,但其数值有意义
print("正在显示原始彩色图和HSV图...")
cv2.waitKey(0)
cv2.destroyAllWindows()
# 在HSV空间中基于颜色进行分割 (示例:查找图像中的蓝色区域)
# 定义HSV空间中蓝色的范围 (需要根据实际图像和环境调整这些值)
# H: 色相 (0-179 in OpenCV, 不是360)
# S: 饱和度 (0-255)
# V: 亮度 (0-255)
lower_blue = np.array([100, 50, 50])
upper_blue = np.array([140, 255, 255])
# cv2.inRange() 函数用于根据颜色范围创建二值掩码
# 参数1: 输入图像 (通常是HSV图像)
# 参数2: 颜色范围的下限
# 参数3: 颜色范围的上限
mask = cv2.inRange(img_hsv, lower_blue, upper_blue)
# 使用掩码提取蓝色区域
# cv2.bitwise_and() 用于执行按位与操作
# 参数1: 第一个输入数组
# 参数2: 第二个输入数组 (通常是自身,为了保留原图像的通道信息)
# mask 参数: 指定一个操作掩码,只有掩码中非零对应的元素才会被处理
blue_segment = cv2.bitwise_and(img_color, img_color, mask=mask)
cv2.imshow('Blue Mask', mask) # 显示二值掩码,白色区域是蓝色
cv2.imshow('Blue Segment', blue_segment) # 显示提取出的蓝色区域
print("正在显示蓝色掩码和蓝色区域...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法加载图像文件 ‘{image_path}'”)
“`
在HSV空间中进行颜色分割是一个非常常见的应用。通过cv2.inRange()
创建一个二值掩码,掩码中白色(255)像素表示该位置的颜色在指定范围内,黑色(0)像素表示不在。然后可以使用这个掩码通过cv2.bitwise_and()
来提取原始图像中对应区域。
6. 在图像上绘图:点、线、矩形、圆与文本
OpenCV提供了方便的函数可以在图像上绘制各种几何形状和文本,这对于标注图像、创建视觉效果或进行调试都非常有用。注意,这些绘图函数通常会直接修改输入的图像。
“`python
import cv2
import numpy as np
创建一个黑色的空白图像 (高400,宽600,3通道,uint8类型)
用于绘制形状的“画布”
canvas = np.zeros((400, 600, 3), dtype=’uint8′)
6.1 绘制线段
cv2.line(img, pt1, pt2, color, thickness)
img: 要在其上绘制的图像
pt1: 起点坐标 (x, y)
pt2: 终点坐标 (x, y)
color: 线的颜色 (BGR格式的元组或列表)
thickness: 线的粗细 (像素)
cv2.line(canvas, (50, 50), (550, 50), (0, 255, 0), 3) # 绘制一条绿色直线
6.2 绘制矩形
cv2.rectangle(img, pt1, pt2, color, thickness)
pt1: 矩形左上角坐标 (x, y)
pt2: 矩形右下角坐标 (x, y)
thickness: 线的粗细。-1表示填充整个矩形。
cv2.rectangle(canvas, (100, 100), (300, 200), (255, 0, 0), 2) # 绘制一个蓝色矩形框
cv2.rectangle(canvas, (350, 100), (550, 200), (0, 0, 255), -1) # 绘制一个填充的红色矩形
6.3 绘制圆形
cv2.circle(img, center, radius, color, thickness)
center: 圆心坐标 (x, y)
radius: 圆的半径
cv2.circle(canvas, (300, 300), 50, (0, 255, 255), -1) # 绘制一个填充的黄色圆
6.4 绘制点 (其实就是半径很小的圆形)
绘制一个白点在指定位置
cv2.circle(canvas, (50, 350), 2, (255, 255, 255), -1)
6.5 绘制文本
cv2.putText(img, text, org, fontFace, fontScale, color, thickness, lineType)
text: 要绘制的文本字符串
org: 文本框左下角坐标 (x, y)
fontFace: 字体类型 (如 cv2.FONT_HERSHEY_SIMPLEX)
fontScale: 字体大小的缩放因子
color: 文本颜色 (BGR)
thickness: 线的粗细
lineType: 线的类型 (可选,如 cv2.LINE_AA 反锯齿效果)
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(canvas, ‘Hello, OpenCV!’, (50, 380), font, 1, (255, 255, 255), 2, cv2.LINE_AA)
显示绘制结果
cv2.imshow(‘Drawing on Canvas’, canvas)
print(“正在显示绘制结果…”)
cv2.waitKey(0)
cv2.destroyAllWindows()
“`
这些绘图函数的使用都非常直观,主要参数包括:绘制在哪张图上、位置/坐标、颜色、粗细等。颜色同样使用BGR格式。
7. 图像基本变换:缩放、平移、旋转与仿射变换
对图像进行几何变换是非常常见的需求,比如调整大小、矫正角度、移动位置等。OpenCV提供了强大的函数来执行这些操作。
7.1 缩放 (Resizing)
改变图像的尺寸。
“`python
import cv2
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path)
if img is not None:
# 获取原始图像的尺寸
height, width = img.shape[:2]
print(f”原始尺寸: 宽度={width}, 高度={height}”)
# 方法1: 按比例缩放
# cv2.resize(src, dsize, fx, fy, interpolation)
# src: 输入图像
# dsize: 输出图像尺寸 (width, height)。如果fx或fy不为0,dsize可以为None。
# fx: 沿水平轴的缩放因子 (相对于原始宽度)
# fy: 沿垂直轴的缩放因子 (相对于原始高度)
# interpolation: 插值方法 (cv2.INTER_AREA 用于缩小,cv2.INTER_CUBIC 和 cv2.INTER_LINEAR 用于放大)
# 将图像缩小到原来的一半
img_small = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA)
print(f"缩小后尺寸: {img_small.shape[1]}x{img_small.shape[0]}")
# 方法2: 指定目标尺寸
# 将图像缩放到指定大小 (例如 300x200)
desired_width = 300
desired_height = 200
# 注意 dsize 的顺序是 (宽度, 高度)
img_resized = cv2.resize(img, (desired_width, desired_height), interpolation=cv2.INTER_LINEAR)
print(f"指定尺寸后: {img_resized.shape[1]}x{img_resized.shape[0]}")
cv2.imshow('Original Image', img)
cv2.imshow('Resized Image (Half Size)', img_small)
cv2.imshow('Resized Image (300x200)', img_resized)
print("正在显示缩放结果...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法加载图像文件 ‘{image_path}'”)
“`
插值方法决定了在缩放时如何计算新像素的值。cv2.INTER_AREA
通常用于缩小,因为它能有效避免像素失真。cv2.INTER_CUBIC
和cv2.INTER_LINEAR
用于放大时效果较好,其中cv2.INTER_CUBIC
(双三次插值)通常能提供更好的视觉效果,但计算量更大。
7.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
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path)
if img is not None:
height, width = img.shape[:2]
# 定义平移量 (例如向右平移50像素,向下平移100像素)
tx = 50
ty = 100
# 创建平移矩阵 M
# M = [[1, 0, tx], [0, 1, ty]]
M = np.float32([[1, 0, tx], [0, 1, ty]])
print(f"平移矩阵 M:\n{M}")
# cv2.warpAffine() 执行仿射变换
# 参数1: 输入图像
# 参数2: 2x3的变换矩阵 M (数据类型必须是float32)
# 参数3: 输出图像的尺寸 (width, height)
# dsize: 输出图像的尺寸,通常设置为原始图像的尺寸以避免裁剪或增加黑边,
# 但如果平移超出边界,部分内容会丢失。
# 如果希望包含平移后的全部内容,需要计算新的尺寸
img_translated = cv2.warpAffine(img, M, (width, height))
cv2.imshow('Original Image', img)
cv2.imshow('Translated Image', img_translated)
print("正在显示平移结果...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法加载图像文件 ‘{image_path}'”)
“`
cv2.warpAffine()
是执行各种仿射变换的核心函数。平移是最简单的仿射变换之一。
7.3 旋转 (Rotation)
绕着某个中心点旋转图像。同样需要创建变换矩阵,然后使用cv2.warpAffine()
。OpenCV提供了cv2.getRotationMatrix2D()
来方便地生成旋转矩阵。
“`python
import cv2
import numpy as np
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path)
if img is not None:
height, width = img.shape[:2]
# 定义旋转中心、角度和缩放比例
# center: 旋转中心 (x, y)。通常是图像中心 (width/2, height/2)
# angle: 旋转角度 (以度为单位),正值表示逆时针旋转
# scale: 缩放比例。1.0表示不缩放。
center = (width // 2, height // 2) # 整数除法
angle = 45 # 逆时针旋转45度
scale = 1.0
# cv2.getRotationMatrix2D() 获取旋转的2x3仿射变换矩阵
# 参数1: 旋转中心
# 参数2: 旋转角度
# 参数3: 缩放比例
M = cv2.getRotationMatrix2D(center, angle, scale)
print(f"旋转矩阵 M:\n{M}")
# cv2.warpAffine() 执行旋转
# dsize: 输出图像的尺寸。如果旋转后图像超出原始尺寸,部分内容会被裁剪。
# 如果希望包含旋转后的全部内容且不裁剪,需要计算新的尺寸
img_rotated = cv2.warpAffine(img, M, (width, height))
cv2.imshow('Original Image', img)
cv2.imshow('Rotated Image (45 degrees)', img_rotated)
print("正在显示旋转结果...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法加载图像文件 ‘{image_path}'”)
“`
注意,默认的旋转可能会裁剪掉旋转后超出原始图像尺寸的部分。如果需要保留所有内容,你需要计算旋转后图像的最小边界矩形,并用其尺寸作为cv2.warpAffine()
的dsize
参数,同时调整旋转中心和变换矩阵以适应新的尺寸。但这对于初学者来说稍显复杂,这里仅展示基本旋转。
7.4 仿射变换 (Affine Transformation)
仿射变换包括平移、旋转、缩放、剪切(shear)等。在仿射变换下,平行线仍然保持平行。仿射变换由一个2×3的矩阵定义,需要知道输入图像中的三个非共线点以及它们在输出图像中对应的位置,就可以计算出变换矩阵。
“`python
import cv2
import numpy as np
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path)
if img is not None:
height, width = img.shape[:2]
# 定义输入图像的三个点 (必须是np.float32类型)
pts1 = np.float32([[50, 50], [200, 50], [50, 200]])
# 定义这些点在输出图像中的对应位置
pts2 = np.float32([[10, 100], [200, 50], [100, 250]])
# cv2.getAffineTransform() 根据三对对应点计算2x3的仿射变换矩阵
M = cv2.getAffineTransform(pts1, pts2)
print(f"仿射变换矩阵 M:\n{M}")
# cv2.warpAffine() 应用仿射变换
# dsize 通常设置为原始图像的尺寸
img_affine = cv2.warpAffine(img, M, (width, height))
cv2.imshow('Original Image', img)
cv2.imshow('Affine Transformed Image', img_affine)
print("正在显示仿射变换结果...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法加载图像文件 ‘{image_path}'”)
“`
仿射变换在图像校正、图像配准等领域有广泛应用。
8. 图像滤波:模糊与平滑
滤波是图像处理中常用的技术,用于去除噪声、平滑图像或突出边缘等。滤波操作通常涉及卷积(Convolution),即将一个小的矩阵(称为卷积核或滤波器)在图像上滑动,对每个像素及其邻域进行加权求和,得到新像素的值。
8.1 卷积概念简介 (非代码)
想象一个3×3的滤波器在图像上滑动。当滤波器中心位于某个像素(x, y)时,它会覆盖以(x, y)为中心的9个像素。新像素(x, y)的值就是这9个像素值与滤波器对应位置权重相乘再求和的结果。通过设计不同的滤波器,可以实现不同的效果。
8.2 常见的模糊滤波器
模糊是一种常见的平滑操作,可以减少图像中的噪声。常见的模糊滤波器包括均值滤波、高斯滤波、中值滤波等。
“`python
import cv2
import numpy as np
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path)
if img is not None:
# 8.2.1 均值滤波 (Averaging)
# cv2.blur(src, ksize)
# src: 输入图像
# ksize: 卷积核大小 (width, height)。核内所有像素权重相同,取平均值。
img_mean_blur = cv2.blur(img, (5, 5)) # 使用5×5的核进行均值滤波
# 8.2.2 高斯滤波 (Gaussian Blurring)
# cv2.GaussianBlur(src, ksize, sigmaX)
# ksize: 高斯核的大小 (width, height)。必须是正奇数。
# sigmaX: 高斯核在X方向的标准差。sigmaY可以省略,默认为sigmaX。sigma越大,模糊越明显。
# 如果sigmaX为0,则根据核大小计算。建议指定核大小,让函数自动计算sigma。
img_gaussian_blur = cv2.GaussianBlur(img, (5, 5), 0) # 使用5x5的核进行高斯滤波
# 8.2.3 中值滤波 (Median Blurring)
# cv2.medianBlur(src, ksize)
# ksize: 中值滤波核的大小。必须是大于1的奇数。
# 中值滤波用核窗口内的像素值的中值来替代中心像素的值。对椒盐噪声(salt-and-pepper noise)特别有效。
img_median_blur = cv2.medianBlur(img, 5) # 使用5x5的核进行中值滤波
cv2.imshow('Original Image', img)
cv2.imshow('Mean Blurred', img_mean_blur)
cv2.imshow('Gaussian Blurred', img_gaussian_blur)
cv2.imshow('Median Blurred', img_median_blur)
print("正在显示各种模糊结果...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法加载图像文件 ‘{image_path}'”)
“`
选择哪种模糊方法取决于具体的应用需求。均值滤波简单快速,但可能不够平滑。高斯滤波产生的模糊效果更自然,符合人眼的感知。中值滤波特别擅长去除图像中的椒盐噪声,因为它不会引入新的像素值,而是直接使用邻域像素的中值。
9. 图像阈值化:二值化图像
阈值化是将灰度图像转换为二值图像(每个像素只有黑色和白色)的过程。通常设定一个阈值,像素值高于阈值的设为白色(255),低于阈值的设为黑色(0)。这是图像分割中非常基础且重要的一步。
9.1 全局阈值化 (Simple Thresholding)
对图像中的所有像素使用同一个阈值。
“`python
import cv2
import numpy as np
image_path = ‘path/to/your/image.jpg’
img_gray = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) # 确保加载的是灰度图像
if img_gray is not None:
# cv2.threshold(src, thresh, maxval, type)
# src: 输入的灰度图像
# thresh: 用于分类像素的阈值
# maxval: 当像素值大于(或小于,取决于type)阈值时设定的最大值
# type: 阈值类型 (cv2.THRESH_BINARY, cv2.THRESH_BINARY_INV, cv2.THRESH_TRUNC, cv2.THRESH_TOZERO, cv2.THRESH_TOZERO_INV)
# 返回值:
# ret: 使用的阈值 (对于cv2.THRESH_BINARY等简单类型,就是输入的thresh)
# dst: 阈值化后的二值图像
# 例1: 二值化 (大于127的设为255,否则设为0)
ret, thresh1 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)
# 例2: 反二值化 (大于127的设为0,否则设为255)
ret, thresh2 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY_INV)
# 例3: 截断 (大于127的设为127,否则不变)
ret, thresh3 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TRUNC)
# 例4: 阈值归零 (大于127的不变,否则设为0)
ret, thresh4 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO)
# 例5: 反阈值归零 (小于127的不变,否则设为0)
ret, thresh5 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO_INV)
cv2.imshow('Original Grayscale', img_gray)
cv2.imshow('Binary Threshold (127)', thresh1)
cv2.imshow('Binary Inverse (127)', thresh2)
cv2.imshow('Truncate (127)', thresh3)
cv2.imshow('To Zero (127)', thresh4)
cv2.imshow('To Zero Inverse (127)', thresh5)
print("正在显示各种全局阈值化结果...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法加载灰度图像文件 ‘{image_path}'”)
“`
全局阈值化简单快速,但如果图像的亮度分布不均匀,效果可能不好。
9.2 自适应阈值化 (Adaptive Thresholding)
自适应阈值化根据像素的局部邻域来计算阈值,这对于亮度变化较大的图像效果更好。
“`python
import cv2
import numpy as np
image_path = ‘path/to/your/image.jpg’
img_gray = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
if img_gray is not None:
# cv2.adaptiveThreshold(src, maxval, adaptiveMethod, thresholdType, blockSize, C)
# src: 输入的灰度图像
# maxval: 当像素值满足条件时设定的最大值
# adaptiveMethod: 自适应阈值的计算方法 (cv2.ADAPTIVE_THRESH_MEAN_C 或 cv2.ADAPTIVE_THRESH_GAUSSIAN_C)
# thresholdType: 阈值类型 (通常是 cv2.THRESH_BINARY 或 cv2.THRESH_BINARY_INV)
# blockSize: 用于计算阈值的像素邻域大小。必须是奇数且大于1。
# C: 从计算出的均值或加权平均值中减去的常数。用于微调阈值。
# 方法1: 均值法 (ADAPTIVE_THRESH_MEAN_C)
# 阈值是邻域像素的平均值减去C
thresh_mean = cv2.adaptiveThreshold(img_gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY, 11, 2) # 11x11的邻域,C=2
# 方法2: 高斯法 (ADAPTIVE_THRESH_GAUSSIAN_C)
# 阈值是邻域像素加权平均值(高斯权重)减去C
thresh_gaussian = cv2.adaptiveThreshold(img_gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2) # 11x11的邻域,C=2
cv2.imshow('Original Grayscale', img_gray)
cv2.imshow('Adaptive Mean Threshold', thresh_mean)
cv2.imshow('Adaptive Gaussian Threshold', thresh_gaussian)
print("正在显示自适应阈值化结果...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法加载灰度图像文件 ‘{image_path}'”)
“`
自适应阈值化更适合处理光照不均的图像,例如扫描的文档等。
9.3 Otsu’s 方法
Otsu’s 方法(大津法)是一种自动确定全局最佳阈值的方法。它假设图像包含两类像素(前景和背景),然后计算一个最佳阈值,使得两类像素的类内方差最小或类间方差最大。
“`python
import cv2
import numpy as np
image_path = ‘path/to/your/image.jpg’
img_gray = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
if img_gray is not None:
# 使用Otsu’s方法自动寻找阈值
# cv2.threshold() 函数结合 cv2.THRESH_OTSU 标志使用
# 在这种情况下,输入的 thresh 参数会被忽略,函数会自动计算最佳阈值
ret_otsu, thresh_otsu = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
print(f"Otsu方法计算出的最佳阈值是: {ret_otsu}")
cv2.imshow('Original Grayscale', img_gray)
cv2.imshow('Otsu Threshold', thresh_otsu)
print("正在显示Otsu阈值化结果...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法加载灰度图像文件 ‘{image_path}'”)
“`
Otsu方法常用于处理直方图呈双峰分布的图像,能自动得到一个比较好的二值化效果。
10. 边缘检测:Canny算法
边缘是图像中像素强度发生显著变化的地方,它们通常对应着物体的轮廓或不同区域的分界线。边缘检测是图像分析和特征提取的重要步骤。Canny边缘检测算法是目前被广泛应用且效果较好的边缘检测算法之一。
Canny算法主要包含以下几个步骤:
1. 噪声抑制: 使用高斯滤波平滑图像,去除噪声。
2. 计算图像梯度: 检测图像中的强度梯度,找出可能的边缘。
3. 非极大值抑制: 细化边缘,只保留最强的边缘像素,抑制弱的或冗余的边缘像素。
4. 双阈值处理: 使用两个阈值(高阈值和低阈值)来确定最终的边缘。高于高阈值的梯度点被确认为强边缘,低于低阈值的被抑制。介于两者之间的点,如果与强边缘连接,则也被视为边缘(称为弱边缘连接)。
5. 边缘跟踪: 通过抑制孤立的弱边缘并保留与强边缘相连的弱边缘,完成最终的边缘图。
OpenCV将Canny算法封装在一个函数中:
“`python
import cv2
import numpy as np
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) # Canny通常在灰度图上进行
if img is not None:
# cv2.Canny(image, threshold1, threshold2, apertureSize, L2gradient)
# image: 输入的灰度图像
# threshold1: 第一个阈值 (低阈值)
# threshold2: 第二个阈值 (高阈值)。建议 threshold2 是 threshold1 的 2到3倍。
# apertureSize: Sobel算子的大小,用于计算图像梯度 (默认3,可选3, 5, 7)。
# L2gradient: 布尔值,如果为True,使用L2范数计算梯度幅度;如果为False(默认),使用L1范数。L2通常更精确但计算量更大。
edges = cv2.Canny(img, 100, 200) # 低阈值100,高阈值200
cv2.imshow('Original Grayscale', img)
cv2.imshow('Canny Edges', edges)
print("正在显示Canny边缘检测结果...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法加载灰度图像文件 ‘{image_path}'”)
“`
选择合适的低阈值和高阈值是Canny算法的关键,它们直接影响检测到的边缘数量和连接性。
11. 轮廓检测与分析
轮廓(Contours)是指图像中具有相同颜色或强度的连续曲线。它们是图像中对象边界的有用表示。在图像分析中,找到对象的轮廓是常见的任务,比如形状分析、目标识别和测量等。
重要前提: 轮廓检测通常在二值图像(如通过阈值化或Canny边缘检测得到的图像)上进行。
“`python
import cv2
import numpy as np
image_path = ‘path/to/your/image.jpg’
为了演示轮廓检测,我们先加载一个图像,进行二值化或边缘检测
这里使用简单的二值化作为示例
img = cv2.imread(image_path)
if img is not None:
# 1. 转换为灰度图 (如果不是二值图或灰度图,先转换)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 2. 进行阈值化或边缘检测得到二值图像
# ret, thresh = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)
thresh = cv2.Canny(img_gray, 100, 200) # 也可以使用Canny边缘作为输入
# 注意:cv2.findContours() 函数在某些OpenCV版本中会修改输入图像,
# 所以最好在图像的副本上进行操作
thresh_copy = thresh.copy()
# 3. 查找轮廓
# cv2.findContours(image, mode, method)
# image: 8位单通道图像 (二值图像或Canny边缘图像)
# mode: 轮廓检索模式
# cv2.RETR_EXTERNAL: 只检索最外层轮廓
# cv2.RETR_LIST: 检索所有轮廓,不建立任何层级关系
# cv2.RETR_CCOMP: 检索所有轮廓,建立双层结构 (外部轮廓和内部空洞轮廓)
# cv2.RETR_TREE: 检索所有轮廓,建立完整的层级树
# method: 轮廓逼近方法
# cv2.CHAIN_APPROX_NONE: 存储所有轮廓点
# cv2.CHAIN_APPROX_SIMPLE: 压缩水平、垂直和对角线段,只保留端点 (节省内存)
# 返回值:
# contours: 一个Python列表,其中每个轮廓都被存储为一个NumPy数组,包含轮廓点的(x, y)坐标。
# hierarchy: 可选的输出,描述轮廓的层级关系。
contours, hierarchy = cv2.findContours(thresh_copy, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
print(f"找到 {len(contours)} 个轮廓")
# 4. 绘制轮廓
# cv2.drawContours(image, contours, contourIdx, color, thickness)
# image: 要在上面绘制轮廓的图像 (可以是原始彩色图或空白画布)
# contours: 轮廓列表 (由cv2.findContours()返回)
# contourIdx: 要绘制的轮廓的索引。-1表示绘制所有轮廓。
# color: 轮廓颜色 (BGR)
# thickness: 轮廓线的粗细
# 在原始彩色图像的副本上绘制所有轮廓
img_contours = img.copy()
# 随机给每个轮廓分配颜色(可选)
# for i, contour in enumerate(contours):
# color = (np.random.randint(0, 256), np.random.randint(0, 256), np.random.randint(0, 256))
# cv2.drawContours(img_contours, contours, i, color, 2)
# 绘制所有轮廓为绿色,粗细为2
cv2.drawContours(img_contours, contours, -1, (0, 255, 0), 2)
cv2.imshow('Original Image', img)
cv2.imshow('Threshold/Edges for Contours', thresh) # 显示用于查找轮廓的二值图/边缘图
cv2.imshow('Contours', img_contours)
print("正在显示轮廓检测结果...")
cv2.waitKey(0)
cv2.destroyAllWindows()
# 5. 轮廓分析 (示例:计算轮廓面积和周长,绘制最小外接矩形)
print("\n--- 轮廓分析示例 ---")
for i, contour in enumerate(contours):
area = cv2.contourArea(contour) # 计算轮廓面积
perimeter = cv2.arcLength(contour, True) # 计算轮廓周长 (True表示是闭合曲线)
print(f"轮廓 {i+1}: 面积={area:.2f}, 周长={perimeter:.2f}")
# 获取轮廓的边界矩形
# x, y 是矩形左上角坐标,w, h 是宽度和高度
x, y, w, h = cv2.boundingRect(contour)
# 在原始图像的副本上绘制边界矩形 (红色)
cv2.rectangle(img.copy(), (x, y), (x+w, y+h), (0, 0, 255), 2)
# 可以显示每个轮廓的边界矩形,这里只展示一个
# cv2.imshow(f'Contour {i+1} Bounding Box', cv2.rectangle(img.copy(), (x, y), (x+w, y+h), (0, 0, 255), 2))
# cv2.waitKey(0)
# 获取轮廓的最小外接圆
#(x, y), radius = cv2.minEnclosingCircle(contour)
#center = (int(x), int(y))
#radius = int(radius)
#cv2.circle(img.copy(), center, radius, (255, 255, 0), 2) # 黄色圆
# 获取轮廓的中心矩 (Moments)
#M = cv2.moments(contour)
#if M["m00"] != 0:
# cx = int(M["m10"] / M["m00"]) # 中心点x坐标
# cy = int(M["m01"] / M["m00"]) # 中心点y坐标
# cv2.circle(img.copy(), (cx, cy), 5, (255, 0, 255), -1) # 紫色点表示中心
# 为了演示,显示最后一个轮廓的边界矩形 (或你可以循环显示)
if len(contours) > 0:
x, y, w, h = cv2.boundingRect(contours[-1])
img_bbox = cv2.rectangle(img.copy(), (x, y), (x+w, y+h), (0, 0, 255), 2)
cv2.imshow('Last Contour Bounding Box', img_bbox)
print("正在显示最后一个轮廓的边界矩形...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法加载图像文件 ‘{image_path}'”)
“`
轮廓检测和分析是识别和测量图像中对象的基础。你可以根据轮廓的面积、周长、形状特征等进行进一步的过滤和分类。
12. 视频处理:读取、显示与保存视频
OpenCV不仅可以处理静态图像,还能方便地处理视频流,无论是本地视频文件还是摄像头输入。视频可以看作是一系列连续的图像帧。
12.1 读取与显示视频/摄像头
“`python
import cv2
创建VideoCapture对象
参数可以是视频文件路径 (如 ‘my_video.mp4’)
或者摄像头索引 (通常0表示默认摄像头)
cap = cv2.VideoCapture(0) # 尝试打开第一个可用的摄像头
检查VideoCapture是否成功打开
if not cap.isOpened():
print(“错误: 无法打开摄像头或视频文件。请检查设备或文件路径。”)
else:
print(“摄像头/视频流已成功打开。按 ‘q’ 退出。”)
while(True):
# cap.read() 读取视频流的下一帧
# 返回值:
# ret: 布尔值,如果读取成功则为 True
# frame: 读取到的视频帧 (NumPy数组)
ret, frame = cap.read()
# 如果读取失败 (例如视频结束或摄像头断开),则 ret 为 False
if not ret:
print("无法接收帧 (视频结束?)。退出...")
break
# 在此处可以对每一帧进行图像处理操作
# 例如,将帧转换为灰度图
# gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 显示原始帧
cv2.imshow('Video Frame', frame)
# 显示处理后的帧 (如果进行了处理)
# cv2.imshow('Processed Frame', gray_frame)
# cv2.waitKey() 参数表示等待按键的毫秒数。
# 这里设置为1,表示等待1毫秒。这样可以快速循环处理帧,形成视频流的效果。
# 它返回按下的键的ASCII值。
# ord('q') 获取字符 'q' 的ASCII值
if cv2.waitKey(1) & 0xFF == ord('q'):
break # 如果按下 'q' 键,退出循环
# 释放VideoCapture对象和所有窗口
cap.release()
cv2.destroyAllWindows()
“`
cv2.VideoCapture()
返回一个VideoCapture对象,通过这个对象可以读取视频的属性(如帧率、分辨率)以及逐帧读取图像。cap.read()
是循环处理视频的核心。cv2.waitKey(1)
的参数1表示每隔1毫秒检查一次键盘输入,这是实现视频播放的关键。
12.2 保存视频
除了读取,你还可以将处理后的视频帧写入文件,从而保存视频。这需要创建一个cv2.VideoWriter
对象。
“`python
import cv2
import numpy as np
打开摄像头或视频文件
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print(“错误: 无法打开摄像头。”)
else:
# 获取视频帧的宽度和高度
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) # 获取到的FPS可能不准确
fps = 20 # 示例:指定输出视频的帧率为20 FPS
# 定义视频编码器 (Codec) 和创建VideoWriter对象
# FourCC 是一个4字节的代码,用于指定视频编码器
# 常见的 FourCC 编码器:
# 对于 AVI 文件: cv2.VideoWriter_fourcc('X', 'V', 'I', 'D')
# 对于 MP4 文件 (macOS, Linux): cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
# 对于 MP4 文件 (Windows): cv2.VideoWriter_fourcc('M', 'J', 'P', 'G') 可能需要根据系统和安装的编码器调整
# 推荐使用 MP4 和合适的编码器以获得更好的兼容性和压缩率
fourcc = cv2.VideoWriter_fourcc(*'XVID') # 例如,使用XVID编码器保存为AVI
# 创建VideoWriter对象
# 参数1: 输出文件路径
# 参数2: FourCC 编码器
# 参数3: 帧率 (FPS)
# 参数4: 帧的大小 (width, height)
out = cv2.VideoWriter('output_video.avi', fourcc, fps, (frame_width, frame_height))
print(f"正在录制视频到 'output_video.avi',分辨率 {frame_width}x{frame_height} @ {fps} FPS。按 'q' 停止录制。")
while(cap.isOpened()):
ret, frame = cap.read()
if ret:
# 在这里可以对每一帧进行图像处理,例如转换为灰度
# processed_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 注意灰度图只有一个通道,需要指定 VideoWriter 为灰度模式
# 写入原始帧到文件
out.write(frame)
# 显示帧
cv2.imshow('Frame', frame)
# 按 'q' 退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
else:
break
# 释放资源
cap.release()
out.release()
cv2.destroyAllWindows()
print("录制停止,资源已释放。")
“`
保存视频需要注意编码器(FourCC)的选择和输出文件的扩展名。不同的操作系统和安装的编码器可能支持不同的组合。XVID
和 .avi
文件是一个比较常见的兼容组合。对于MP4,你可能需要尝试'mp4v'
或'MJPG'
或其他编码器。
13. 综合案例:简单的图像处理应用
让我们将前面学到的一些基础知识结合起来,创建一个简单的应用:从摄像头读取视频流,将每一帧转换为灰度图并进行Canny边缘检测,然后显示原始帧和处理后的边缘帧。
“`python
import cv2
import numpy as np
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print(“错误: 无法打开摄像头。”)
else:
print(“摄像头已打开。正在显示原始帧和边缘检测结果。按 ‘q’ 退出。”)
while(True):
ret, frame = cap.read()
if not ret:
print("无法读取帧。退出...")
break
# 将彩色帧转换为灰度图
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 对灰度帧进行高斯模糊 (有助于减少边缘检测的噪声)
blurred_frame = cv2.GaussianBlur(gray_frame, (5, 5), 0)
# 进行Canny边缘检测
edges = cv2.Canny(blurred_frame, 50, 150) # 阈值需要根据实际情况调整
# 显示原始彩色帧
cv2.imshow('Original Frame', frame)
# 显示边缘检测结果帧
cv2.imshow('Canny Edges', edges)
# 按 'q' 键退出循环
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 释放摄像头资源和窗口
cap.release()
cv2.destroyAllWindows()
“`
这个简单的案例展示了如何将不同的OpenCV函数组合起来处理视频流。你可以尝试将其他图像处理技术(如阈值化、轮廓检测)应用到视频帧中。
14. 进阶之路:OpenCV的更多功能
本教程仅仅触及了OpenCV的皮毛。OpenCV库非常庞大,包含了计算机视觉领域的许多高级功能。在你掌握了基础后,可以继续探索以下令人兴奋的主题:
- 特征检测与描述: SIFT, SURF (需要contrib模块), ORB, FAST等,用于在不同图像中寻找和匹配特征点。
- 特征匹配与图像拼接: 利用特征点将多张图片拼接成全景图。
- 对象检测: 使用Haar级联分类器(如人脸检测)或更先进的深度学习方法(如YOLO, SSD)。
- 对象跟踪: 在视频序列中跟踪特定对象。
- 相机校准与三维重建: 消除相机畸变,从二维图像重建三维信息。
- 机器学习: 内置了一些传统的机器学习算法,如SVM、K-Means等。
- 深度学习模块: OpenCV提供了DNN模块,可以加载和运行预训练的深度学习模型。
- 形态学操作: 腐蚀、膨胀、开运算、闭运算等,用于处理二值图像。
要学习这些更高级的功能,查阅OpenCV官方文档、示例代码以及相关的书籍和在线课程是非常有益的。
15. 总结与展望
恭喜你完成了OpenCV Python初学者教程的学习!我们从OpenCV和Python的基础知识出发,学习了图像的加载、显示和保存,掌握了图像属性和像素访问,探索了不同的色彩空间,学习了在图像上绘图、进行几何变换、图像滤波、阈值化、边缘检测以及轮廓检测等核心操作,最后还了解了视频处理的基础。
OpenCV是一个功能强大的工具箱,为你的计算机视觉项目提供了坚实的基础。通过不断实践和探索,你可以利用这些工具解决各种实际问题,例如:
- 构建一个简单的图像编辑器
- 开发一个人脸或物体识别应用
- 创建自动化图像分析脚本
- 参与更复杂的计算机视觉研究或开发项目
计算机视觉领域正在飞速发展,与深度学习等技术的结合更是带来了前所未有的可能性。OpenCV也在不断更新和完善,以适应新的技术和应用需求。
现在,你已经具备了入门OpenCV Python的知识。最好的学习方法是动手实践。尝试修改本教程中的代码,用自己的图片和视频进行实验,思考如何将不同的技术组合起来解决特定的问题。不断练习,不断探索,你将在计算机视觉的世界里取得更大的进步!
祝你在计算机视觉的学习旅程中一切顺利!