Python OpenCV 快速入门:从零开始玩转图像与视频处理
引言:探索计算机视觉的神奇世界
计算机视觉是一门让计算机“看懂”世界的科学。它涉及到如何让机器获取、处理、分析和理解图像和视频数据。从人脸识别、自动驾驶到医疗影像分析、工业缺陷检测,计算机视觉的应用无处不在,极大地改变着我们的生活。
而 OpenCV(Open Source Computer Vision Library)是目前最流行、功能最强大的开源计算机视觉库之一。它包含了丰富的算法和函数,涵盖了图像处理、特征检测、目标识别、机器学习等多个领域。虽然 OpenCV 最初是用 C++ 编写的,但它提供了对多种编程语言的接口,其中 Python 接口因其简洁易用的语法和强大的生态系统(如 NumPy、Matplotlib)而备受青睐。
本文旨在为 Python 初学者提供一个 OpenCV 的快速入门指南。我们将从安装开始,逐步深入到图像和视频的基本操作,包括读取、显示、保存、修改像素、颜色空间转换、基本图形绘制、图像算术运算、阈值处理、滤波、边缘检测,以及视频的读取与处理。通过阅读本文,你将能够掌握 OpenCV 的核心概念和常用函数,为进一步探索更高级的计算机视觉技术打下坚实的基础。
无需担心你是否有深厚的计算机视觉背景,只要你具备基本的 Python 编程知识,就可以跟随本文一起踏上 OpenCV 的奇妙旅程。
第一步:准备环境——安装 Python 与 OpenCV
在开始之前,确保你的计算机上已经安装了 Python。推荐安装 Python 3.6 或更高版本。你可以从 Python 官方网站下载安装包:https://www.python.org/downloads/。
安装完 Python 后,强烈建议使用虚拟环境(Virtual Environment)。虚拟环境可以隔离不同项目所需的库版本,避免冲突。常用的虚拟环境工具有 venv
(Python 3 自带)或 conda
。
使用 venv
创建虚拟环境的步骤如下:
- 打开终端或命令提示符。
- 导航到你的项目目录。
- 执行命令创建虚拟环境(例如,命名为
myenv
):
bash
python -m venv myenv - 激活虚拟环境:
- Windows:
myenv\Scripts\activate
- macOS/Linux:
source myenv/bin/activate
- Windows:
激活虚拟环境后,你的终端提示符前会显示虚拟环境的名称(如 (myenv)
)。现在,我们可以使用 pip
来安装 OpenCV。
安装 OpenCV 的 Python 包(opencv-python
):
bash
pip install opencv-python numpy matplotlib
这里我们不仅安装了 opencv-python
,还安装了 numpy
和 matplotlib
。numpy
是 OpenCV Python 接口的底层依赖,图像数据在 OpenCV 中通常以 NumPy 数组的形式表示。matplotlib
则是一个强大的绘图库,可以用来方便地显示图像,尤其是在调试和对比图像处理效果时非常有用。
安装完成后,你可以通过简单的 Python 脚本来验证安装是否成功:
python
import cv2
print(cv2.__version__)
运行这段代码,如果能成功打印出 OpenCV 的版本号,说明安装成功。
第二步:图像的载入、显示与保存
计算机视觉的核心是处理图像。首先,我们需要知道如何在 OpenCV 中加载一张图片到程序中,如何在窗口中显示它,以及如何将处理后的图片保存到文件中。
OpenCV 使用 NumPy 数组来表示图像。对于彩色图像,通常是一个三维数组 [height, width, channels]
,通道顺序默认为 BGR(蓝、绿、红,而不是常见的 RGB)。对于灰度图像,是一个二维数组 [height, width]
。
2.1 载入图像 (cv2.imread
)
cv2.imread()
函数用于从指定路径加载图像。
“`python
import cv2
import numpy as np
替换为你自己的图片路径
image_path = ‘path/to/your/image.jpg’ # 例如:’./data/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(f”图像加载成功,尺寸:{img.shape}”) # shape 返回 (高, 宽, 通道数) 或 (高, 宽)
print(f”图像数据类型:{img.dtype}”) # dtype 通常是 uint8 (无符号8位整数)
“`
注意: cv2.imread()
返回的图像如果加载失败(例如文件不存在或路径错误),会是一个 None
对象。因此,在后续处理前检查 img is None
是一个好习惯。
2.2 显示图像 (cv2.imshow
, cv2.waitKey
, cv2.destroyAllWindows
)
cv2.imshow()
函数用于在窗口中显示图像。它需要两个参数:窗口名称(字符串)和要显示的图像对象。
“`python
假设 img 已经成功加载
if img is not None:
# 在名为 ‘Original Image’ 的窗口中显示图像
cv2.imshow(‘Original Image’, img)
# cv2.waitKey() 函数等待键盘事件
# 参数是等待的毫秒数。0 表示无限期等待直到按下任意键
# 返回按下的键的ASCII码
# 这是一个非常重要的函数,用于保持窗口显示,否则窗口会一闪而过
print("按下任意键关闭窗口...")
cv2.waitKey(0)
# cv2.destroyAllWindows() 函数销毁所有由OpenCV创建的窗口
# 如果只想销毁特定窗口,可以使用 cv2.destroyWindow('窗口名称')
cv2.destroyAllWindows()
“`
完整的加载和显示示例:
“`python
import cv2
import numpy as np
import os
假设你的图片在当前脚本的同级目录下,名为 ‘test_image.jpg’
如果没有,请创建一个或使用一个已有的图片文件,并修改 image_path
image_path = ‘test_image.jpg’ # 或者一个完整的路径,如 ‘C:/Users/YourUser/Pictures/test_image.jpg’
为了演示,如果图片不存在,创建一个简单的空白图片
if not os.path.exists(image_path):
print(f”图片文件 ‘{image_path}’ 不存在,创建一个空白图片用于演示。”)
# 创建一个500×800像素的白色彩色图片 (高度500,宽度800)
# BGR顺序,(255, 255, 255) 是白色
blank_img = np.full((500, 800, 3), 255, dtype=np.uint8)
# 在图片上写一些字
cv2.putText(blank_img, “Hello, OpenCV!”, (50, 250), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 0, 0), 3)
cv2.imwrite(image_path, blank_img)
print(f”空白图片已创建为 ‘{image_path}'”)
载入图像
img = cv2.imread(image_path)
检查图像是否加载成功
if img is None:
print(f”错误:无法加载图像文件 {image_path}”)
else:
# 显示图像
cv2.imshow(‘Loaded Image’, img)
# 等待按键,然后关闭窗口
print("图片加载并显示成功。按下任意键关闭窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows()
“`
运行这段代码,如果图片存在,它会显示图片;如果不存在,它会创建一个简单的空白图片然后显示。
2.3 保存图像 (cv2.imwrite
)
cv2.imwrite()
函数用于将图像保存到文件。它需要两个参数:保存路径(包含文件名和扩展名)和要保存的图像对象。文件扩展名决定了保存的格式(如 .jpg
, .png
, .bmp
等)。
“`python
假设 img 已经成功加载并且你对它进行了一些处理(稍后介绍处理方法)
现在你想保存处理后的图像
if img is not None:
# 假设 img 是处理后的图像
# 保存为 PNG 格式
output_path_png = ‘processed_image.png’
success_png = cv2.imwrite(output_path_png, img)
if success_png:
print(f"图像已成功保存为 {output_path_png}")
else:
print(f"保存图像失败 {output_path_png}")
# 也可以保存为 JPG 格式
output_path_jpg = 'processed_image.jpg'
# 对于JPG,可以指定压缩质量 (0-100),默认为95
# cv2.imwrite(output_path_jpg, img, [cv2.IMWRITE_JPEG_QUALITY, 90])
success_jpg = cv2.imwrite(output_path_jpg, img)
if success_jpg:
print(f"图像已成功保存为 {output_path_jpg}")
else:
print(f"保存图像失败 {output_path_jpg}")
“`
第三步:图像的基本属性与像素操作
在 OpenCV 中,图像被视为 NumPy 数组。这使得我们可以利用 NumPy 强大的数组操作能力来处理图像。
3.1 图像的属性
我们可以像访问 NumPy 数组一样访问图像的属性:
img.shape
: 返回一个元组,表示图像的尺寸。彩色图像是(height, width, channels)
,灰度图像是(height, width)
。img.size
: 返回图像的总像素数(height * width * channels 或 height * width)。img.dtype
: 返回图像的数据类型。通常是uint8
(无符号8位整数),表示每个像素通道的取值范围是 0 到 255。
“`python
假设 img 已经成功加载
if img is not None:
print(“— 图像属性 —“)
print(f”图像形状 (高, 宽, 通道): {img.shape}”)
print(f”总像素数: {img.size}”)
print(f”数据类型: {img.dtype}”)
# 如果是彩色图像,通道数通常是3
if len(img.shape) == 3:
height, width, channels = img.shape
print(f"高度: {height} 像素")
print(f"宽度: {width} 像素")
print(f"通道数: {channels}")
else: # 灰度图像
height, width = img.shape
print(f"高度: {height} 像素")
print(f"宽度: {width} 像素")
print(f"通道数: 1 (灰度)")
“`
3.2 访问和修改像素值
你可以使用 NumPy 索引来访问和修改图像的像素值。记住 OpenCV 使用 [y, x]
或 [行, 列]
的顺序来访问像素,这与 NumPy 习惯的 [行, 列]
一致。对于彩色图像,还需要指定通道索引(0 for B, 1 for G, 2 for R)。
“`python
假设 img 已经成功加载
if img is not None:
# 获取图像尺寸
height, width = img.shape[:2] # 获取高和宽,忽略通道数
# 访问某个像素点的值 (例如,第100行,第150列的像素)
# 对于彩色图像,返回 (B, G, R) 值的列表
# 对于灰度图像,返回单个灰度值
pixel = img[100, 150]
print(f"\n像素 (150, 100) 的值: {pixel}") # 注意:这里通常说像素 (x, y) 但索引是 (y, x)
# 如果是彩色图像,访问特定通道的值
if len(img.shape) == 3:
blue_channel_value = img[100, 150, 0] # 蓝色通道
green_channel_value = img[100, 150, 1] # 绿色通道
red_channel_value = img[100, 150, 2] # 红色通道
print(f"像素 (150, 100) 的蓝色值: {blue_channel_value}")
print(f"像素 (150, 100) 的绿色值: {green_channel_value}")
print(f"像素 (150, 100) 的红色值: {red_channel_value}")
# 修改某个像素点的值 (例如,将像素 (200, 250) 设置为红色)
if len(img.shape) == 3:
# BGR 顺序,红色是 (0, 0, 255)
img[200, 250] = [0, 0, 255]
print(f"像素 (250, 200) 已修改为红色。")
# 或者修改特定通道
img[200, 250, 0] = 255 # 将该像素的蓝色通道设置为最大值
# 注意:直接访问和修改像素虽然直观,但在处理大图像时效率较低
# OpenCV 和 NumPy 提供了更高效的函数来处理图像块或整个图像
# 显示修改后的图像
cv2.imshow('Modified Image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
“`
3.3 图像 ROI (Region of Interest)
图像的 ROI 是图像中的一个子区域。你可以使用 NumPy 切片来提取 ROI。
“`python
假设 img 已经成功加载
if img is not None:
# 提取图像左上角 100×150 像素的区域作为 ROI
# 注意:切片顺序是 [start_row:end_row, start_col:end_col]
# 这里的行是高度方向 (y), 列是宽度方向 (x)
# 因此,从第0行到第100行 (不包含100),从第0列到第150列 (不包含150)
roi = img[0:100, 0:150]
print(f"提取的 ROI 尺寸: {roi.shape}")
# 在窗口中显示 ROI
cv2.imshow('Image ROI', roi)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 你也可以将一个 ROI 复制到图像的另一个位置
# 确保目标区域和 ROI 的尺寸相同
# 例如,将左上角的 ROI 复制到右下角
# 首先确定右下角区域的起始点
if img.shape[0] > 100 and img.shape[1] > 150:
# 计算目标区域的起始行和列
target_start_row = img.shape[0] - 100
target_start_col = img.shape[1] - 150
# 复制 ROI 到目标区域
img[target_start_row:img.shape[0], target_start_col:img.shape[1]] = roi
# 显示修改后的图像
cv2.imshow('Image with ROI Copied', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print("图像尺寸太小,无法演示 ROI 复制。")
“`
第四步:颜色空间转换
颜色空间是描述颜色的数学模型。常见的颜色空间有 RGB、BGR、灰度、HSV、HLS 等。OpenCV 支持多种颜色空间的转换。最常见的转换是从 BGR(OpenCV默认)到灰度,或者从 BGR 到 HSV(色相、饱和度、亮度),HSV 空间在基于颜色分割物体时非常有用。
cv2.cvtColor()
函数用于进行颜色空间转换。
“`python
假设 img 是一个彩色图像 (BGR)
if img is not None and len(img.shape) == 3:
# 将 BGR 图像转换为灰度图像
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print(f"灰度图像尺寸: {gray_img.shape}") # shape 会变为 (高, 宽)
# 将 BGR 图像转换为 HSV 图像
hsv_img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
print(f"HSV 图像尺寸: {hsv_img.shape}") # shape 仍然是 (高, 宽, 3)
# 显示原始彩色图像、灰度图像和 HSV 图像
cv2.imshow('Original BGR Image', img)
cv2.imshow('Grayscale Image', gray_img)
cv2.imshow('HSV Image', hsv_img) # HSV 图像直接显示可能看起来怪异,因为它是三通道数据
# 等待按键关闭窗口
cv2.waitKey(0)
cv2.destroyAllWindows()
# 你也可以分离或合并通道
# 分离通道 (B, G, R)
b, g, r = cv2.split(img)
# 显示分离后的通道(它们是灰度图像)
cv2.imshow('Blue Channel', b)
cv2.imshow('Green Channel', g)
cv2.imshow('Red Channel', r)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 合并通道(注意顺序是 B, G, R)
# 例如,创建一个只有红色通道的彩色图像(其他通道全为零)
zeros = np.zeros(img.shape[:2], dtype=np.uint8) # 创建一个同尺寸的零矩阵
red_only_img = cv2.merge([zeros, zeros, r])
cv2.imshow('Red Only Image', red_only_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(“图像不是彩色图像或加载失败,跳过颜色空间转换演示。”)
``
cv2.COLOR_BGR2GRAY
常用的颜色空间转换标志有:
*: BGR 到灰度
cv2.COLOR_BGR2HSV
*: BGR 到 HSV
cv2.COLOR_HSV2BGR
*: HSV 到 BGR
cv2.COLOR_BGR2RGB`: BGR 到 RGB (用于 Matplotlib 显示,因为它默认 RGB)
*
第五步:在图像上绘制图形和文字
OpenCV 提供了一系列函数可以在图像上绘制各种基本图形,如线条、圆形、矩形、椭圆、多边形以及添加文本。
这些绘制函数通常需要以下参数:
* img
: 要绘制的图像。
* color
: 图形的颜色,对于彩色图像是一个 BGR 元组 (例如,(255, 0, 0) 是蓝色)。
* thickness
: 绘制的线条粗细。如果设置为 -1,则填充图形。
“`python
首先创建一个空白图像作为画布 (例如,一个 500×500 的黑色图像)
canvas = np.zeros((500, 500, 3), dtype=np.uint8) # 黑色是 (0, 0, 0)
1. 绘制线条
cv2.line(img, start_point, end_point, color, thickness)
cv2.line(canvas, (0, 0), (500, 500), (255, 0, 0), 5) # 从左上角到右下角的蓝色线条
2. 绘制矩形
cv2.rectangle(img, pt1, pt2, color, thickness)
pt1: 矩形左上角坐标 (x, y)
pt2: 矩形右下角坐标 (x, y)
cv2.rectangle(canvas, (100, 100), (400, 400), (0, 255, 0), 3) # 绿色的框
3. 绘制圆形
cv2.circle(img, center, radius, color, thickness)
cv2.circle(canvas, (250, 250), 100, (0, 0, 255), -1) # 红色的实心圆
4. 绘制椭圆 (相对复杂,可以先跳过)
cv2.ellipse(img, center, axes, angle, startAngle, endAngle, color, thickness)
center: 中心点 (x, y)
axes: 半长轴和半短轴长度 (major_axis_length, minor_axis_length)
angle: 椭圆旋转角度
startAngle, endAngle: 绘制圆弧的起始和结束角度
cv2.ellipse(canvas, (250, 250), (150, 50), 0, 0, 180, (255, 255, 0), 2) # 黄色的半椭圆
5. 绘制多边形
cv2.polylines(img, pts, isClosed, color, thickness)
pts: 多边形顶点坐标数组的数组 (例如:[np.array([[x1,y1], [x2,y2], …]], np.int32))
isClosed: 是否闭合多边形
pts = np.array([[50, 500], [150, 400], [250, 500], [350, 400], [450, 500]], np.int32)
pts = pts.reshape((-1, 1, 2)) # 将顶点坐标调整为所需的形状 [[point1], [point2], …]
cv2.polylines(canvas, [pts], False, (255, 255, 255), 2) # 白色的折线
6. 添加文本
cv2.putText(img, text, org, fontFace, fontScale, color, thickness, lineType)
text: 要添加的文本字符串
org: 文本左下角坐标 (x, y)
fontFace: 字体类型 (如 cv2.FONT_HERSHEY_SIMPLEX)
fontScale: 字体大小比例
lineType: 线条类型 (如 cv2.LINE_AA 用于抗锯齿)
cv2.putText(canvas, ‘Hello OpenCV!’, (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv2.LINE_AA) # 白色文本
显示绘制结果
cv2.imshow(‘Drawing on Canvas’, canvas)
cv2.waitKey(0)
cv2.destroyAllWindows()
“`
第六步:图像算术运算与逻辑运算
OpenCV 提供了多种图像算术运算(如加、减、乘、除)和逻辑运算(如与、或、非、异或)。这些操作在图像混合、创建掩膜、提取特定区域等方面非常有用。
6.1 图像加法
可以使用 cv2.add()
或 NumPy 的 +
运算符进行图像加法。需要注意的是,cv2.add()
是饱和运算(saturated operation),结果会限制在 0-255 范围内,超出 255 的值会被截断为 255。而 NumPy 的 +
是模运算(modulo operation),结果会进行取模,可能导致意外的颜色变化。对于图像通常应使用 cv2.add()
。
图像加法常用于增加图像亮度。将图像与一个常数相加,可以使每个像素的值增加。
“`python
假设 img 已经加载 (uint8 类型)
if img is not None:
# 创建一个与 img 相同尺寸的常量图像 (例如,每个像素值为 50)
M = np.ones(img.shape, dtype=”uint8″) * 50
# 使用 cv2.add() 进行图像加法(增加亮度)
added_img_cv2 = cv2.add(img, M)
# 使用 NumPy 的 + 进行图像加法(可能出现取模)
# added_img_np = img + M # 结果可能会与 cv2.add 不同
# 显示原始图像和加法结果
cv2.imshow('Original Image', img)
cv2.imshow('Image Added (cv2.add)', added_img_cv2)
# cv2.imshow('Image Added (NumPy +)', added_img_np) # 可以对比看区别
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(“图像加载失败,跳过加法演示。”)
“`
6.2 图像混合 (Weighted Addition)
图像混合是一种特殊的加法,可以根据权重将两张图像叠加在一起,常用于创建图像叠加效果(如水印)。这通过 cv2.addWeighted()
函数实现。
公式为:dst = alpha * img1 + beta * img2 + gamma
其中 alpha
和 beta
是两张图像的权重,gamma
是一个偏置值。通常设置 alpha + beta = 1
,gamma = 0
。
“`python
需要两张尺寸相同的图片进行混合
假设 img1 和 img2 已经加载且尺寸相同
为了演示,我们使用同一张图片的两个副本
如果没有第二张图片,使用上面的空白图片和加载的图片进行混合
img1 = img # 或者加载另一张图片 ‘path/to/another_image.jpg’
img2 = blank_img # 使用之前创建的空白图片
确保两张图片存在且尺寸相同
if img1 is not None and img2 is not None and img1.shape == img2.shape:
# 设置混合权重
alpha = 0.7 # img1 的权重
beta = 0.3 # img2 的权重 (1 – alpha)
gamma = 0 # 偏置值
# 执行图像混合
blended_img = cv2.addWeighted(img1, alpha, img2, beta, gamma)
# 显示混合结果
cv2.imshow('Image 1', img1)
cv2.imshow('Image 2', img2)
cv2.imshow('Blended Image', blended_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(“需要两张尺寸相同的图片进行混合演示,或者图片加载失败。”)
“`
6.3 位逻辑运算
位逻辑运算包括 AND (&), OR (|), NOT (~), XOR (^)。这些操作在图像掩膜处理、提取图像特定区域等方面非常有用。
例如,位 AND 运算可以用于根据一个掩膜图像提取原始图像中的特定区域。掩膜图像通常是二值的(只有 0 和 255),与原始图像进行 AND 运算后,掩膜中像素值为 255 的区域会保留原始图像的像素值,而像素值为 0 的区域会变为 0(黑色)。
“`python
假设 img 已经加载
if img is not None:
# 创建一个掩膜图像
# 掩膜通常与原始图像尺寸相同
# 创建一个黑色的掩膜
mask = np.zeros(img.shape[:2], dtype=”uint8″) # 灰度图像作为掩膜
# 在掩膜上绘制一个白色 (255) 的圆形区域
# 这个白色区域将是我们要从原始图像中提取的部分
center_x, center_y = img.shape[1] // 2, img.shape[0] // 2
radius = min(center_x, center_y) // 2
cv2.circle(mask, (center_x, center_y), radius, 255, -1) # 在中心绘制一个实心白圆
# 显示原始图像和创建的掩膜
cv2.imshow('Original Image', img)
cv2.imshow('Mask', mask)
# 对原始图像和掩膜进行位 AND 运算
# 只有在掩膜中是白色(255)的区域,原始图像对应像素才会保留
# OpenCV的按位操作函数会自动处理彩色图像和灰度掩膜
result = cv2.bitwise_and(img, img, mask=mask)
# 显示位 AND 运算结果
cv2.imshow('Result of Bitwise AND (Masking)', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(“图像加载失败,跳过位运算演示。”)
“`
类似地,还有 cv2.bitwise_or()
, cv2.bitwise_not()
, cv2.bitwise_xor()
函数。
第七步:基本的图像处理技术
OpenCV 提供了大量图像处理函数,这里我们介绍几个最基础和常用的。
7.1 图像阈值处理 (Thresholding)
阈值处理是一种简单的图像分割技术,它将灰度图像转换为二值图像。通过设定一个阈值,大于阈值的像素被设置为一个最大值(通常是 255),小于或等于阈值的像素被设置为一个最小值(通常是 0)。
cv2.threshold()
函数用于全局阈值处理。它返回两个值:应用的阈值和阈值处理后的图像。
“`python
假设 img 已经加载
if img is not None:
# 将图像转换为灰度(阈值处理通常在灰度图像上进行)
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 应用阈值处理
# ret: 返回的阈值 (如果使用 OTVU 等方法,这个值会自动计算)
# binary_img: 阈值处理后的二值图像
# 参数解释:
# src: 输入图像 (灰度图像)
# thresh: 全局阈值
# maxval: 当像素值大于 (或小于,取决于 type) 阈值时设定的最大值
# type: 阈值类型
# cv2.THRESH_BINARY: > thresh -> maxval, <= thresh -> 0
# cv2.THRESH_BINARY_INV: > thresh -> 0, <= thresh -> maxval
# cv2.THRESH_TRUNC: > thresh -> thresh, else unchanged
# cv2.THRESH_TOZERO: > thresh -> unchanged, else -> 0
# cv2.THRESH_TOZERO_INV: > thresh -> 0, else unchanged
# 示例:二值化处理,阈值为 127
ret, binary_img = cv2.threshold(gray_img, 127, 255, cv2.THRESH_BINARY)
# 示例:反二值化处理
ret, binary_inv_img = cv2.threshold(gray_img, 127, 255, cv2.THRESH_BINARY_INV)
# 示例:使用 OTSU 方法自动计算最佳阈值
# 在 type 参数中加入 cv2.THRESH_OTSU
# 注意:使用 OTSU 时,threshold 参数会被忽略,OpenCV 会自己计算
ret_otsu, otsu_binary_img = cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
print(f"\nOTSU 方法计算的阈值: {ret_otsu}")
# 显示结果
cv2.imshow('Original Grayscale', gray_img)
cv2.imshow('Binary Threshold (127)', binary_img)
cv2.imshow('Binary Inverse Threshold (127)', binary_inv_img)
cv2.imshow('OTSU Binary Threshold', otsu_binary_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(“图像加载失败,跳过阈值处理演示。”)
“`
当图像光照不均匀时,全局阈值可能效果不佳。这时可以使用自适应阈值 (cv2.adaptiveThreshold
),它会根据像素的局部邻域来计算阈值。
7.2 图像平滑/模糊 (Smoothing/Blurring)
图像模糊是一种低通滤波,用于减少图像噪声和细节。OpenCV 提供了多种模糊技术:
- 均值滤波 (
cv2.blur
): 使用一个矩形内核,计算内核覆盖区域内所有像素的平均值作为中心像素的新值。 - 高斯滤波 (
cv2.GaussianBlur
): 使用高斯核进行卷积,距离中心越近的像素权重越大。这是最常用的滤波方法之一,能有效去除高斯噪声。 - 中值滤波 (
cv2.medianBlur
): 使用内核覆盖区域内像素的中值作为中心像素的新值。对于去除椒盐噪声(salt-and-pepper noise)非常有效。 - 双边滤波 (
cv2.bilateralFilter
): 可以在平滑图像的同时保留边缘。
“`python
假设 img 已经加载
if img is not None:
# 添加一些噪声用于演示(可选)
# noisy_img = img + np.random.normal(0, 25, img.shape).astype(np.uint8) # 添加高斯噪声,可能需要 clip 到 0-255
# 或者更简单地,使用一个稍微有点噪声的图片进行测试
# 定义内核大小 (必须是正奇数,如 3x3, 5x5 等)
kernel_size = (5, 5)
median_kernel_size = 5 # 中值滤波只需要一个奇数
# 均值滤波
blurred_mean = cv2.blur(img, kernel_size)
# 高斯滤波
# kernel_size: 高斯核的标准差,如果为0,则根据内核大小计算
blurred_gaussian = cv2.GaussianBlur(img, kernel_size, 0)
# 中值滤波
blurred_median = cv2.medianBlur(img, median_kernel_size)
# 双边滤波 (d: 像素邻域直径, sigmaColor: 颜色空间的标准差, sigmaSpace: 坐标空间的标准差)
# 较大的 sigmaColor 值意味着更远的颜色会混合在一起
# 较大的 sigmaSpace 值意味着更远的像素会相互影响
blurred_bilateral = cv2.bilateralFilter(img, 9, 75, 75)
# 显示结果
cv2.imshow('Original Image', img)
cv2.imshow('Mean Blur', blurred_mean)
cv2.imshow('Gaussian Blur', blurred_gaussian)
cv2.imshow('Median Blur', blurred_median)
cv2.imshow('Bilateral Filter', blurred_bilateral)
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(“图像加载失败,跳过模糊演示。”)
“`
7.3 边缘检测 (Edge Detection)
边缘是图像中像素强度发生显著变化的地方,它们通常对应于物体的轮廓或边界。边缘检测是许多计算机视觉任务的重要预处理步骤。OpenCV 提供了多种边缘检测算法,其中 Canny 边缘检测器 (cv2.Canny
) 是一个非常流行且效果好的算法。
Canny 边缘检测包括几个步骤:高斯模糊、计算图像梯度、非极大值抑制、双阈值处理和边缘跟踪。
cv2.Canny()
函数需要输入图像(通常是灰度图像)和两个阈值 threshold1
和 threshold2
。
“`python
假设 img 已经加载
if img is not None:
# 转换为灰度图像(Canny通常在灰度图像上运行)
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 应用 Canny 边缘检测
# threshold1: 最小阈值
# threshold2: 最大阈值
# apertureSize: Sobel 算子的孔径大小 (默认 3)
# L2gradient: 计算梯度幅度的标志 (默认 False,使用 L1 范数;True 使用 L2 范数)
edges = cv2.Canny(gray_img, 100, 200) # 使用100和200作为双阈值
# 显示结果
cv2.imshow('Original Grayscale', gray_img)
cv2.imshow('Canny Edges', edges)
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(“图像加载失败,跳过边缘检测演示。”)
``
threshold1
调整和
threshold2可以影响检测到的边缘数量和强度。通常,
threshold2大于
threshold1`。
第八步:处理视频流
除了静态图像,OpenCV 还可以轻松处理视频文件或从摄像头捕获视频流。视频本质上是一系列连续的图像帧。
8.1 读取视频文件或摄像头
cv2.VideoCapture
对象用于读取视频。传入文件路径用于读取视频文件,传入设备索引(通常是 0 代表默认摄像头)用于读取摄像头。
“`python
创建 VideoCapture 对象
cap = cv2.VideoCapture(‘path/to/your/video.mp4’) # 读取视频文件
cap = cv2.VideoCapture(0) # 读取默认摄像头 (设备索引 0)
检查 VideoCapture 是否成功打开
if not cap.isOpened():
print(“错误:无法打开视频流或摄像头。”)
exit() # 如果无法打开,退出程序
循环读取视频帧
while True:
# cap.read() 方法读取下一帧
# success: 布尔值,表示是否成功读取帧
# frame: 读取到的图像帧 (NumPy 数组)
success, frame = cap.read()
# 如果帧读取不成功 (例如视频结束或摄像头断开),则退出循环
if not success:
print("无法接收帧 (视频流结束?)。退出...")
break
# 在这里可以对每一帧进行图像处理
# 例如,将帧转换为灰度
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 显示原始帧和处理后的帧
cv2.imshow('Original Frame', frame)
cv2.imshow('Grayscale Frame', gray_frame)
# cv2.waitKey() 用于控制帧率并检测按键
# 参数是等待按键的毫秒数
# 如果在这个时间内按下指定的键,则返回该键的 ASCII 码
# 例如,等待 1 毫秒,如果按下 'q' 键 (ASCII 码是 113),则退出循环
# cv2.waitKey(1) 表示每隔 1 毫秒检查一次按键
if cv2.waitKey(1) & 0xFF == ord('q'):
break # 按下 'q' 键退出循环
循环结束后,释放 VideoCapture 对象并关闭所有窗口
cap.release()
cv2.destroyAllWindows()
“`
运行这段代码,它会打开你的摄像头窗口(如果成功),并实时显示彩色和灰度画面。按下 ‘q’ 键可以退出。
8.2 保存视频
除了读取视频,我们还可以将处理后的视频帧写入到新的视频文件中。这需要使用 cv2.VideoWriter
对象。
创建 cv2.VideoWriter
需要指定:
* 输出文件路径。
* FourCC 编码:指定视频编解码器。不同的 FourCC 代码对应不同的编码格式(如 MP4, AVI 等),兼容性可能因操作系统和安装的解码器而异。常见的 FourCC 代码有 'XVID'
, 'MP4V'
, 'MJPG'
等。可以使用 cv2.VideoWriter_fourcc()
创建。
* 帧率 (FPS):每秒显示的帧数。
* 帧的大小 (Frame Size):视频的分辨率 (width, height)
。这必须与要写入的帧的尺寸匹配。
“`python
假设我们已经打开了 VideoCapture 对象 ‘cap’ 并成功读取了第一帧 ‘frame’
获取原始视频流的帧率和帧尺寸 (如果从文件读取)
对于摄像头,可能需要手动设定帧率和尺寸
if cap.isOpened():
# 获取帧尺寸 (宽度, 高度)
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# 获取帧率 (可能不准确,尤其是对于摄像头)
# fps = int(cap.get(cv2.CAP_PROP_FPS)) # 有时返回0或不准确
# 手动设定一个帧率,例如 20 FPS
fps = 20
# 定义 FourCC 编码
# 例如,使用 XVID 编码器,通常生成 .avi 文件
fourcc = cv2.VideoWriter_fourcc(*'XVID')
# 或者使用 MP4V 编码器,通常生成 .mp4 文件
# fourcc = cv2.VideoWriter_fourcc(*'mp4v') # Windows 上可能需要安装额外的 codec
# 创建 VideoWriter 对象
output_filename = 'output_video.avi'
out = cv2.VideoWriter(output_filename, fourcc, fps, (frame_width, frame_height))
# 检查 VideoWriter 是否成功打开
if not out.isOpened():
print(f"错误:无法创建视频写入对象或打开文件 {output_filename}")
# 如果无法写入,继续读取和显示,但不保存
out = None
else:
print(f"视频写入对象已创建,将保存到 {output_filename}")
# 进入循环读取和处理帧,并将帧写入文件
print("\n正在处理视频流,按下 'q' 键停止...")
while cap.isOpened():
success, frame = cap.read()
if not success:
print("无法接收帧。退出...")
break
# 在这里可以对 'frame' 进行任意处理,例如转换为灰度
# processed_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 注意:如果处理后改变了通道数 (如灰度),需要确保 VideoWriter 的 FourCC 和帧尺寸与写入的帧匹配
# 如果写入灰度帧到彩色视频文件可能会出错,反之亦然
# 最简单的方式是写入与原始帧尺寸和通道数相同的帧
frame_to_write = frame # 或者 processed_frame (如果 out 设定正确)
# 将处理后的帧写入视频文件
if out is not None:
out.write(frame_to_write)
# 显示帧 (可选)
cv2.imshow('Frame', frame)
# cv2.imshow('Processed Frame', processed_frame) # 如果你进行了处理
# 检测按键退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 循环结束后,释放所有对象
cap.release()
if out is not None:
out.release()
cv2.destroyAllWindows()
print("视频处理和保存完成。")
else:
print(“无法打开视频流或摄像头,跳过视频处理演示。”)
``
output_video.avi` 文件中。按下 ‘q’ 键停止录制和显示。
运行这段代码,如果摄像头成功打开,它会显示摄像头画面,并将画面保存到
第九步:更进一步的探索
本文只是 OpenCV Python 接口的入门指南。OpenCV 的功能远不止于此。你可以继续学习以下内容:
- 图像变换: 仿射变换、透视变换等。
- 形态学操作: 腐蚀、膨胀、开运算、闭运算等,用于处理二值图像。
- 直方图: 计算和分析图像的直方图,用于图像增强、对比度调整等。
- 轮廓检测: 查找和分析图像中的轮廓,用于物体检测和测量。
- 特征检测与匹配: SIFT, SURF, ORB 等算法用于查找图像中的关键点并进行匹配。
- 目标检测: Haar Cascades(人脸检测)、HOG+SVM、以及基于深度学习的方法(DNN 模块)。
- 对象跟踪: CamShift, MeanShift 等算法。
- 机器学习: 集成了一些基本的机器学习算法。
- 相机标定与三维重建。
要深入学习,强烈推荐查阅官方 OpenCV 官方文档 (https://docs.opencv.org/) 和大量的在线教程、书籍。
总结
在本文中,我们带你快速入门了 Python OpenCV 的基础知识。我们学习了:
- 如何安装 Python 和 OpenCV 库。
- 如何载入、显示和保存图像。
- 图像的基本属性和像素操作。
- 常见的颜色空间转换。
- 在图像上绘制基本图形和文本。
- 图像的算术和逻辑运算,以及如何使用掩膜。
- 基本的图像处理技术:阈值处理、模糊和边缘检测。
- 如何读取、处理和保存视频流。
通过这些基础知识,你现在已经具备了使用 OpenCV 进行简单的图像和视频处理的能力。记住,实践是最好的老师。尝试使用你自己的图片和视频,修改代码,探索不同的参数,看看它们会带来什么效果。
计算机视觉是一个充满挑战和机遇的领域。掌握 OpenCV,将为你打开通往这个精彩世界的大门。祝你在 OpenCV 的学习和实践中取得成功!