OpenCV Python 基础指南:从零开始掌握计算机视觉
计算机视觉是当今最热门、发展最快的技术领域之一,它让计算机能够“看懂”世界。从自动驾驶汽车、人脸识别,到医疗影像分析、工业自动化,计算机视觉的应用无处不在。而 OpenCV (Open Source Computer Vision Library) 是目前最流行、功能最强大的开源计算机视觉库之一,它提供了丰富的函数和工具,帮助开发者轻松实现各种计算机视觉任务。
虽然 OpenCV 最初是用 C++ 编写的,但它提供了对多种编程语言的接口,其中 Python 接口因其简洁易懂的语法和强大的生态系统(如 NumPy, Matplotlib)而广受欢迎,成为许多初学者和研究者的首选。
本文将带你从零开始,深入了解如何使用 OpenCV Python 进行基础的图像和视频处理。我们将涵盖环境搭建、基本的图像操作、像素访问、颜色空间转换、图形绘制、图像变换、阈值化、滤波以及简单的视频处理等内容。无论你是一名学生、工程师,还是仅仅对计算机视觉感到好奇,这篇指南都将为你打下坚实的基础。
第一章:环境搭建与初识 OpenCV Python
在开始学习 OpenCV 之前,你需要先搭建好所需的开发环境。
1.1 安装 Python
如果你还没有安装 Python,推荐安装 Python 3.6 或更高版本。一个常用的方式是安装 Anaconda 或 Miniconda,它们自带了 Python 以及许多科学计算所需的库,并且方便管理不同的 Python 环境。
访问 Anaconda 官网 (https://www.anaconda.com/) 或 Miniconda 官网 (https://docs.conda.io/en/latest/miniconda.html) 下载对应你操作系统的安装包并按照指示进行安装。
1.2 安装 OpenCV
安装 OpenCV Python 库非常简单,只需要使用 pip 包管理器:
bash
pip install opencv-python
这条命令会安装 OpenCV 的主要模块。如果你需要一些额外的贡献模块(例如 SIFT, SURF 等专利算法,虽然其中一部分现在已开源),可以安装 opencv-contrib-python
:
bash
pip install opencv-contrib-python
通常情况下,opencv-python
已经足够满足基础学习的需求。
1.3 验证安装
安装完成后,你可以打开 Python 解释器或创建一个 Python 脚本来验证是否安装成功:
python
import cv2
print(cv2.__version__)
如果能够成功导入 cv2
并且打印出版本号,说明 OpenCV Python 已经安装成功。
1.4 为什么选择 Python 和 OpenCV?
- 易学易用: Python 语法简洁,入门门槛低,可以快速实现想法。
- 强大的社区和生态: Python 拥有庞大的社区支持和丰富的第三方库,如 NumPy 用于数值计算(OpenCV 图像本质上是 NumPy 数组)、Matplotlib 用于数据可视化,这些库可以与 OpenCV 无缝协作。
- OpenCV 功能全面: OpenCV 提供了从低级图像处理(滤波、边缘检测)到高级计算机视觉任务(目标检测、跟踪、人脸识别)的各种算法实现。
- 性能: OpenCV 的核心算法是用 C++ 实现的,性能很高。Python 接口只是提供了一种方便的使用方式。
第二章:图片的基本操作
计算机视觉处理的载体是图像和视频。在 OpenCV 中,图像被表示为 NumPy 数组。理解如何读取、显示和保存图像是入门的第一步。
2.1 读取图片 (cv2.imread
)
使用 cv2.imread()
函数可以从文件中读取图像。
“`python
import cv2
指定图片文件路径
image_path = ‘path/to/your/image.jpg’ # 替换为你的图片路径
读取图片
cv2.imread() 默认读取彩色图片
img = cv2.imread(image_path)
检查图片是否成功读取
if img is None:
print(f”错误:无法读取图片文件 {image_path}”)
else:
print(“图片读取成功!”)
# 可以打印图片的一些信息,例如形状
print(f”图片形状: {img.shape}”) # (高度, 宽度, 通道数)
print(f”图片数据类型: {img.dtype}”)
“`
cv2.imread()
函数的第二个参数是一个标志位,用于指定读取图片的格式:
cv2.IMREAD_COLOR
(或 1): 读取彩色图片。图片的透明度会被忽略。这是默认值。cv2.IMREAD_GRAYSCALE
(或 0): 读取灰度图片。cv2.IMREAD_UNCHANGED
(或 -1): 读取图片,包括 alpha 通道(如果存在)。
示例:读取灰度图片
python
gray_img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
if gray_img is not None:
print(f"灰度图片形状: {gray_img.shape}") # (高度, 宽度)
重要提示: 在 OpenCV 中,彩色图片的通道顺序是 BGR (蓝、绿、红),而不是常见的 RGB。这一点在使用 Matplotlib 或其他库处理图像时需要特别注意。
2.2 显示图片 (cv2.imshow
, cv2.waitKey
, cv2.destroyAllWindows
)
读取图片后,你需要将其显示出来才能看到效果。这需要用到 cv2.imshow()
、cv2.waitKey()
和 cv2.destroyAllWindows()
三个函数协同工作。
“`python
import cv2
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path)
if img is not None:
# 创建一个窗口显示图片
# 第一个参数是窗口名称,可以是任意字符串
cv2.imshow(‘Original Image’, img)
# 等待按键
# cv2.waitKey(0) 表示无限期等待,直到任意键被按下
# cv2.waitKey(milliseconds) 表示等待指定的毫秒数,如果期间有键按下则返回按键的 ASCII 值,否则返回 -1
print("按任意键关闭窗口...")
cv2.waitKey(0) # 等待任意键
# 销毁所有 OpenCV 创建的窗口
cv2.destroyAllWindows()
else:
print(f”错误:无法读取图片文件 {image_path}”)
“`
cv2.imshow(window_name, image)
: 在指定名称的窗口中显示图像。窗口会自动根据图片大小调整。cv2.waitKey(delay)
: 这是一个键盘绑定函数。它等待指定的毫秒数看是否有键盘事件。- 如果
delay
为 0,它会无限期等待直到用户按下任何键。 - 如果
delay > 0
,它会等待delay
毫秒。在这段时间内,如果按下任何键,函数会返回按键的 ASCII 值;否则,超时后返回 -1。 - 这个函数对于图像显示是必不可少的,因为它允许 OpenCV 窗口刷新并处理事件。
- 如果
cv2.destroyAllWindows()
: 销毁所有由 OpenCV 创建的高 GUI 窗口。如果你只希望销毁特定窗口,可以使用cv2.destroyWindow(window_name)
。
2.3 保存图片 (cv2.imwrite
)
使用 cv2.imwrite()
函数可以将图像保存到文件。
“`python
import cv2
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path)
if img is not None:
# 保存图片到新的文件,例如保存为PNG格式
output_path = ‘output_image.png’
success = cv2.imwrite(output_path, img)
if success:
print(f"图片已成功保存到 {output_path}")
else:
print(f"错误:保存图片失败到 {output_path}")
# 可以选择保存为灰度图
# gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# gray_output_path = 'output_gray_image.jpg'
# cv2.imwrite(gray_output_path, gray_img)
else:
print(f”错误:无法读取图片文件 {image_path}”)
“`
cv2.imwrite(filename, img[, params])
: 第一个参数是文件名(包含扩展名,OpenCV 会根据扩展名自动判断图片格式,例如 .jpg, .png 等),第二个参数是要保存的图像。
第三章:图像像素操作与 ROI
在 OpenCV 中,图像被当作一个多维的 NumPy 数组来处理。对于彩色图片,它通常是一个 (高度, 宽度, 3) 的数组;对于灰度图片,则是一个 (高度, 宽度) 的数组。了解如何访问和修改像素值以及如何选取感兴趣区域 (ROI) 是进行更复杂操作的基础。
3.1 访问和修改像素值
你可以像操作 NumPy 数组一样访问和修改图像的像素值。记住 OpenCV 图片的坐标系是 (y, x),即 (行, 列),而不是 (x, y)。对于彩色图片,通道顺序是 BGR。
“`python
import cv2
import numpy as np
创建一个简单的黑色图片 (300×300 像素, 3通道)
img = np.zeros((300, 300, 3), np.uint8) # uint8 是图片常用的数据类型 (0-255)
访问一个像素的 BGR 值 (例如访问第 100 行, 第 150 列的像素)
注意:行是 y 坐标,列是 x 坐标
y, x = 100, 150
pixel_bgr = img[y, x]
print(f”像素 ({x},{y}) 的 BGR 值: {pixel_bgr}”)
修改一个像素的颜色 (例如将该像素设置为蓝色)
BGR 顺序: [蓝色值, 绿色值, 红色值]
img[y, x] = [255, 0, 0] # 纯蓝色
访问单个通道的像素值 (例如访问第 50 行, 第 50 列像素的蓝色通道值)
y, x = 50, 50
blue_value = img[y, x, 0] # 0 代表蓝色通道
green_value = img[y, x, 1] # 1 代表绿色通道
red_value = img[y, x, 2] # 2 代表红色通道
print(f”像素 ({x},{y}) 的蓝色值: {blue_value}”)
修改一个区域的像素值 (例如将图片左上角 50×100 像素区域设置为绿色)
ROI: img[y_start:y_end, x_start:x_end]
img[0:50, 0:100] = [0, 255, 0] # 纯绿色
cv2.imshow(‘Modified Image’, img)
print(“按任意键关闭窗口…”)
cv2.waitKey(0)
cv2.destroyAllWindows()
“`
直接访问和修改单个像素效率不高,通常建议使用 NumPy 的切片和操作来处理区域或整个图像,这得益于 NumPy 的向量化操作。
3.2 图像的属性
你可以使用 NumPy 的属性来获取图像的信息:
img.shape
: 返回一个元组 (高度, 宽度, 通道数)。对于灰度图,是 (高度, 宽度)。img.size
: 返回图片的总像素数(高度 * 宽度 * 通道数)。img.dtype
: 返回图片的像素数据类型,通常是uint8
。
“`python
import cv2
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path)
if img is not None:
print(f”图片形状 (高, 宽, 通道): {img.shape}”)
print(f”图片总像素数: {img.size}”)
print(f”图片数据类型: {img.dtype}”)
else:
print(f”错误:无法读取图片文件 {image_path}”)
“`
3.3 感兴趣区域 (Region of Interest – ROI)
通过 NumPy 的切片功能,你可以轻松地从图像中提取出一个矩形区域作为 ROI。这个 ROI 本质上是原始图像的一个视图(view),对 ROI 的修改也会反映到原始图像上。
“`python
import cv2
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path)
if img is not None:
# 假设我们要提取图片中心区域的 ROI
height, width = img.shape[:2]
# 定义 ROI 的坐标 (左上角和右下角)
# 提取中心 100×150 的区域
start_row, start_col = int(height * 0.5) – 50, int(width * 0.5) – 75
end_row, end_col = start_row + 100, start_col + 150
# 确保坐标在图片范围内
start_row = max(0, start_row)
start_col = max(0, start_col)
end_row = min(height, end_row)
end_col = min(width, end_col)
# 提取 ROI
roi = img[start_row:end_row, start_col:end_col]
# 显示原始图片和 ROI
cv2.imshow('Original Image', img)
cv2.imshow('ROI', roi)
# 可以在 ROI 上进行操作,例如将其颜色反转
# roi = 255 - roi
# cv2.imshow('Inverted ROI', roi) # 注意:对 ROI 的修改会反映到原始图片上!
# cv2.imshow('Original Image After ROI Modify', img) # 原始图片也会变
print("按任意键关闭窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法读取图片文件 {image_path}”)
“`
ROI 的概念在很多计算机视觉任务中非常重要,例如只需要在人脸区域进行识别,或者在特定物体区域进行跟踪。
第四章:颜色空间转换
图像可以表示在不同的颜色空间中。最常见的是 BGR (或 RGB),但还有 HSV、灰度等。了解如何在不同的颜色空间之间转换对于某些任务至关重要,例如基于颜色的物体识别通常在 HSV 空间中进行。
4.1 BGR vs RGB
再次强调,OpenCV 默认使用 BGR 颜色顺序。而许多其他库(如 Matplotlib)使用 RGB。如果你需要将 OpenCV 读取的图像用 Matplotlib 显示,或者将 RGB 图像输入 OpenCV,需要进行颜色空间转换。
4.2 转换为灰度图 (cv2.cvtColor
)
将彩色图像转换为灰度图像是最常见的颜色空间转换之一。灰度图只有一个通道,处理速度更快,并且对于某些算法来说,颜色信息是不必要的甚至会干扰结果。
“`python
import cv2
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path)
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("按任意键关闭窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法读取图片文件 {image_path}”)
“`
cv2.cvtColor(src, code)
函数用于进行颜色空间转换。src
是输入图像,code
是转换标志。cv2.COLOR_BGR2GRAY
就是将 BGR 转换为灰度。
4.3 转换为 HSV 空间 (cv2.cvtColor
)
HSV (Hue, Saturation, Value) 颜色空间将颜色信息 (Hue) 与亮度信息 (Value) 分开,这使得它在进行颜色分割时非常有用,例如根据颜色查找特定物体。
- Hue (色相): 表示颜色的种类,如红色、黄色等,范围通常是 [0, 179] (在 OpenCV 中)。
- Saturation (饱和度): 表示颜色的纯度,越高颜色越鲜艳,范围 [0, 255]。
- Value (明度): 表示颜色的亮度,越高颜色越亮,范围 [0, 255]。
“`python
import cv2
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path)
if img is not None:
# 将 BGR 图片转换为 HSV
hsv_img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 通常你会查看 HSV 图像的某个通道,或者根据 HSV 范围进行颜色分割
# 例如,提取 HSV 图像的 Hue 通道
# hue_channel = hsv_img[:, :, 0]
cv2.imshow('Original BGR Image', img)
cv2.imshow('HSV Image', hsv_img) # 直接显示 HSV 图像通常是黑白的,因为 imshow 只显示 0-255 范围
# cv2.imshow('Hue Channel', hue_channel) # 可以单独显示某个通道
print("按任意键关闭窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法读取图片文件 {image_path}”)
“`
转换回 BGR 也很简单:cv2.cvtColor(hsv_img, cv2.COLOR_HSV2BGR)
.
OpenCV 提供了非常多的颜色空间转换标志,你可以查阅官方文档了解更多。
第五章:在图像上绘制图形和文本
OpenCV 提供了一系列函数,可以在图像上绘制直线、矩形、圆形等基本几何图形以及文本。这对于标记检测到的物体、标注关键信息或简单地创建测试图像非常有用。
绘制函数通常需要以下参数:
- 图像对象 (
img
):要在其上绘制的图像。 - 颜色 (
color
):绘制的颜色,对于彩色图片是 BGR 元组 (B, G, R),对于灰度图是标量值。 - 线宽 (
thickness
):线条的宽度。对于矩形等,如果厚度为 -1,则会填充整个图形。 - 线型 (
lineType
):可选,如cv2.LINE_AA
(抗锯齿,更平滑)。
5.1 绘制直线 (cv2.line
)
cv2.line(img, pt1, pt2, color, thickness, lineType)
: 在 pt1
和 pt2
之间绘制一条直线。pt1
和 pt2
是点的坐标元组 (x, y)
。
5.2 绘制矩形 (cv2.rectangle
)
cv2.rectangle(img, pt1, pt2, color, thickness, lineType)
: 绘制一个矩形,pt1
是左上角顶点坐标 (x1, y1)
,pt2
是右下角顶点坐标 (x2, y2)
。
5.3 绘制圆形 (cv2.circle
)
cv2.circle(img, center, radius, color, thickness, lineType)
: 绘制一个圆形,center
是圆心坐标 (x, y)
,radius
是半径。
5.4 绘制文本 (cv2.putText
)
cv2.putText(img, text, org, fontFace, fontScale, color, thickness, lineType)
: 在图像上绘制文本。
text
: 要绘制的字符串。org
: 文本框的左下角坐标(x, y)
。fontFace
: 字体类型,如cv2.FONT_HERSHEY_SIMPLEX
。fontScale
: 字体大小的缩放因子。color
: 字体颜色。thickness
: 字体线条的粗细。
下面是一个综合示例,在一个黑色背景上绘制各种图形和文本:
“`python
import cv2
import numpy as np
创建一个 512×512 的黑色背景图片
img = np.zeros((512, 512, 3), np.uint8)
1. 绘制一条对角线 (蓝色)
cv2.line(图片, 起点坐标(x,y), 终点坐标(x,y), 颜色(B,G,R), 线宽)
cv2.line(img, (0, 0), (511, 511), (255, 0, 0), 5)
2. 绘制一个矩形 (绿色)
cv2.rectangle(图片, 左上角坐标(x,y), 右下角坐标(x,y), 颜色(B,G,R), 线宽)
cv2.rectangle(img, (384, 0), (510, 128), (0, 255, 0), 3)
3. 绘制一个圆形 (红色), 线宽为 -1 表示填充
cv2.circle(图片, 圆心坐标(x,y), 半径, 颜色(B,G,R), 线宽)
cv2.circle(img, (447, 63), 63, (0, 0, 255), -1)
4. 绘制一个椭圆 (紫色) – 稍微复杂,暂不详细展开,基础先略过
5. 绘制一个多边形 (黄色) – 需要定义顶点,暂不详细展开
6. 绘制文本
cv2.putText(图片, 文本内容, 左下角坐标(x,y), 字体类型, 字体缩放比例, 颜色(B,G,R), 线宽, 线型)
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(img, ‘OpenCV’, (10, 500), font, 4, (255, 255, 255), 2, cv2.LINE_AA)
cv2.imshow(‘Drawing Demo’, img)
print(“按任意键关闭窗口…”)
cv2.waitKey(0)
cv2.destroyAllWindows()
“`
第六章:基本图像变换
图像变换是指对图像进行几何操作,如缩放、平移、旋转等。OpenCV 提供了强大的函数来执行这些变换。
6.1 缩放 (cv2.resize
)
改变图像的大小。这在处理不同分辨率的图像或为某些算法准备输入时非常常见。
cv2.resize(src, dsize[, fx[, fy[, interpolation]]])
:
src
: 输入图像。dsize
: 输出图像的大小元组(width, height)
。如果为(0, 0)
,则根据fx
和fy
计算大小。fx
,fy
: 沿水平和垂直方向的缩放因子。interpolation
: 插值方法。常用的有cv2.INTER_AREA
(缩小图像时常用,避免波纹)、cv2.INTER_CUBIC
(双三次插值,较慢但效果好,放大时常用)、cv2.INTER_LINEAR
(双线性插值,默认,较快)。
“`python
import cv2
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path)
if img is not None:
# 1. 按固定大小缩放
resized_img_fixed = cv2.resize(img, (300, 200)) # 宽度 300, 高度 200
# 2. 按比例缩放
# 缩小到原来的一半
resized_img_scale_down = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA)
# 放大到原来的两倍
resized_img_scale_up = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)
cv2.imshow('Original Image', img)
cv2.imshow('Resized Fixed (300x200)', resized_img_fixed)
cv2.imshow('Resized Scale Down (0.5x)', resized_img_scale_down)
# cv2.imshow('Resized Scale Up (2x)', resized_img_scale_up) # 可能太大,谨慎显示
print("按任意键关闭窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法读取图片文件 {image_path}”)
“`
6.2 平移 (cv2.warpAffine
)
将图像沿着 x 轴和 y 轴移动。平移是一种仿射变换,需要一个 2×3 的变换矩阵 M。
M = [[1, 0, tx],
[0, 1, ty]]
其中 tx
是沿 x 轴的平移量,ty
是沿 y 轴的平移量。
cv2.warpAffine(src, M, dsize)
: 应用仿射变换。
“`python
import cv2
import numpy as np
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path)
if img is not None:
rows, cols = img.shape[:2]
# 定义平移矩阵 M
# 向右平移 100 像素,向下平移 50 像素
tx, ty = 100, 50
M = np.float32([[1, 0, tx], [0, 1, ty]])
# 应用平移变换
# dsize 是输出图像的大小 (宽度, 高度),通常与原图大小相同
translated_img = cv2.warpAffine(img, M, (cols, rows))
cv2.imshow('Original Image', img)
cv2.imshow('Translated Image', translated_img)
print("按任意键关闭窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法读取图片文件 {image_path}”)
“`
注意,超出原图像边界的部分会被裁剪掉,而新进入边界的部分通常填充黑色。
6.3 旋转 (cv2.getRotationMatrix2D
, cv2.warpAffine
)
旋转也是一种仿射变换。OpenCV 提供了 cv2.getRotationMatrix2D()
函数来计算旋转的变换矩阵。
cv2.getRotationMatrix2D(center, angle, scale)
:
center
: 旋转中心,通常取图像中心(cols/2, rows/2)
。angle
: 旋转角度,以度为单位,正值表示逆时针旋转。scale
: 缩放因子。
“`python
import cv2
import numpy as np
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path)
if img is not None:
rows, cols = img.shape[:2]
# 获取旋转矩阵
# 以图像中心为旋转中心,逆时针旋转 45 度,不缩放
M_rotate = cv2.getRotationMatrix2D(((cols-1)/2.0, (rows-1)/2.0), 45, 1)
# 应用旋转变换
rotated_img = cv2.warpAffine(img, M_rotate, (cols, rows))
cv2.imshow('Original Image', img)
cv2.imshow('Rotated Image', rotated_img)
print("按任意键关闭窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法读取图片文件 {image_path}”)
“`
需要注意的是,cv2.warpAffine
的输出图像大小默认为原图大小,旋转后可能导致部分图像内容被裁减。如果需要保留所有内容,需要计算旋转后的新图像大小。
第七章:阈值化处理
阈值化是将灰度图像转换为二值图像的过程,根据像素值与某个阈值的比较结果,将像素设置为最大值(通常是 255)或最小值(通常是 0)。这对于分离前景和背景非常有用。
7.1 简单阈值 (cv2.threshold
)
cv2.threshold(src, thresh, maxval, type)
:
src
: 输入的灰度图像。thresh
: 阈值。maxval
: 当像素值大于(或小于,取决于type
)阈值时设定的最大值。type
: 阈值类型。常用的有:cv2.THRESH_BINARY
:pixel > thresh
->maxval
, else -> 0cv2.THRESH_BINARY_INV
:pixel > thresh
-> 0, else ->maxval
cv2.THRESH_TRUNC
:pixel > thresh
->thresh
, else ->pixel
cv2.THRESH_TOZERO
:pixel > thresh
->pixel
, else -> 0cv2.THRESH_TOZERO_INV
:pixel > thresh
-> 0, else ->pixel
函数返回两个值:实际使用的阈值(对于简单阈值就是输入的 thresh
)和阈值化后的图像。
“`python
import cv2
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) # 读取灰度图
if img is not None:
# 设定阈值
ret, binary_img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
cv2.imshow('Original Grayscale Image', img)
cv2.imshow('Binary Image (Threshold=127)', binary_img)
# 尝试其他类型
# ret, binary_inv_img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
# cv2.imshow('Binary Inverse Image', binary_inv_img)
print("按任意键关闭窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法读取图片文件 {image_path}”)
“`
简单阈值的缺点是阈值是全局固定的,对于光照不均的图像效果不好。
7.2 自适应阈值 (cv2.adaptiveThreshold
)
自适应阈值根据像素的局部邻域来计算阈值,对于光照不均的图像非常有效。
cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)
:
src
: 输入的灰度图像。maxValue
: 设定的最大值。adaptiveMethod
: 自适应方法。cv2.ADAPTIVE_THRESH_MEAN_C
: 阈值是邻域的平均值减去 C。cv2.ADAPTIVE_THRESH_GAUSSIAN_C
: 阈值是邻域的加权平均值(高斯窗口)减去 C。
thresholdType
: 阈值类型,只能是cv2.THRESH_BINARY
或cv2.THRESH_BINARY_INV
。blockSize
: 用于计算阈值的邻域大小。必须是奇数 (例如 3, 5, 7…)。C
: 从平均值或加权平均值中减去的常数。
“`python
import cv2
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) # 读取灰度图
if img is not None:
# 使用自适应阈值
# adaptiveMethod=cv2.ADAPTIVE_THRESH_MEAN_C, blocksize=11, C=2
adaptive_thresh_mean = cv2.adaptiveThreshold(img, 255,
cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY, 11, 2)
# adaptiveMethod=cv2.ADAPTIVE_THRESH_GAUSSIAN_C, blocksize=11, C=2
adaptive_thresh_gaussian = cv2.adaptiveThreshold(img, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2)
cv2.imshow('Original Grayscale Image', img)
cv2.imshow('Adaptive Mean Thresholding', adaptive_thresh_mean)
cv2.imshow('Adaptive Gaussian Thresholding', adaptive_thresh_gaussian)
print("按任意键关闭窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法读取图片文件 {image_path}”)
“`
自适应阈值通常能处理更复杂的光照条件,是文本识别、文档扫描等应用中常用的预处理步骤。
第八章:图像平滑处理(滤波)
图像平滑,或称滤波,是一种减少图像噪声的技术。它通过对图像像素及其周围邻域像素进行计算,用一个新的像素值替换原像素值。OpenCV 提供了多种滤波方法。
8.1 均值滤波 (cv2.blur
)
这是最简单的滤波方法,用像素邻域内的平均值替换该像素值。
cv2.blur(src, ksize)
:
src
: 输入图像。ksize
: 卷积核的大小元组(width, height)
。例如(5, 5)
表示使用 5×5 的邻域。
“`python
import cv2
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path)
if img is not None:
# 应用 5×5 均值滤波
blurred_img = cv2.blur(img, (5, 5))
cv2.imshow('Original Image', img)
cv2.imshow('Blurred Image (Mean)', blurred_img)
print("按任意键关闭窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法读取图片文件 {image_path}”)
“`
均值滤波简单快速,但会模糊边缘。
8.2 高斯滤波 (cv2.GaussianBlur
)
高斯滤波使用一个高斯核(一个二维高斯函数)来计算邻域像素的加权平均值。距离中心像素越近的像素权重越大,这使得高斯滤波在平滑噪声的同时,能比均值滤波更好地保留边缘。
cv2.GaussianBlur(src, ksize, sigmaX[, sigmaY[, borderType]])
:
src
: 输入图像。ksize
: 高斯核的大小,必须是奇数元组(width, height)
。例如(5, 5)
。如果ksize
为(0, 0)
,则根据sigmaX
和sigmaY
计算核大小。sigmaX
: X 方向的标准差。sigmaY
: Y 方向的标准差。如果sigmaY
为 0,则等于sigmaX
。如果两者都为 0,则根据ksize
计算。建议仅指定ksize
和sigmaX
。
“`python
import cv2
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path)
if img is not None:
# 应用 5×5 高斯滤波,sigmaX=0
gaussian_blurred_img = cv2.GaussianBlur(img, (5, 5), 0)
cv2.imshow('Original Image', img)
cv2.imshow('Gaussian Blurred Image', gaussian_blurred_img)
print("按任意键关闭窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法读取图片文件 {image_path}”)
“`
高斯滤波是图像处理中非常常用的一种平滑方法。
8.3 中值滤波 (cv2.medianBlur
)
中值滤波用像素邻域内的中值替换该像素值。它对椒盐噪声(salt-and-pepper noise)特别有效,因为它不会引入新的像素值。
cv2.medianBlur(src, ksize)
:
src
: 输入图像。ksize
: 内核大小,必须是一个大于 1 的奇数整数(例如 3, 5, 7…)。
“`python
import cv2
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path)
if img is not None:
# 添加一些椒盐噪声(这里只是概念说明,实际可能需要手动添加或使用库生成)
# … (假设 img 现在包含椒盐噪声)
# 应用 5x5 中值滤波
median_blurred_img = cv2.medianBlur(img, 5) # 内核大小为 5
cv2.imshow('Original Image (potentially noisy)', img)
cv2.imshow('Median Blurred Image', median_blurred_img)
print("按任意键关闭窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法读取图片文件 {image_path}”)
“`
选择哪种滤波方法取决于图像噪声的类型和后续任务的需求。通常在进行边缘检测等操作之前会先进行平滑处理以减少噪声的影响。
第九章:边缘检测
边缘是图像中像素值发生剧烈变化(通常是梯度大)的地方,它们对应于物体的轮廓和边界。边缘检测是图像特征提取的重要步骤。OpenCV 提供了多种边缘检测算法,其中 Canny 边缘检测器是最常用且效果较好的之一。
9.1 Canny 边缘检测 (cv2.Canny
)
Canny 边缘检测是一个多阶段算法,包括:
1. 噪声抑制: 使用高斯滤波平滑图像。
2. 计算梯度: 计算图像的梯度幅值和方向。
3. 非极大值抑制: 细化边缘,只保留梯度方向上的局部最大值。
4. 双阈值处理与边缘连接: 使用两个阈值(高阈值和低阈值)来确定最终的边缘。梯度大于高阈值的点被确定为强边缘点,小于低阈值的点被抑制,介于两者之间的点如果与强边缘点连通则被认为是弱边缘点并保留,否则也被抑制。
cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]])
:
image
: 输入的灰度图像。threshold1
,threshold2
: 两个阈值,用于双阈值处理。推荐threshold2
是threshold1
的 2 到 3 倍。- 其他参数通常使用默认值即可。
“`python
import cv2
image_path = ‘path/to/your/image.jpg’
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) # Canny 通常处理灰度图
if img is not None:
# 应用 Canny 边缘检测
# 阈值 100 和 200
edges = cv2.Canny(img, 100, 200)
cv2.imshow('Original Grayscale Image', img)
cv2.imshow('Canny Edges', edges)
print("按任意键关闭窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(f”错误:无法读取图片文件 {image_path}”)
“`
Canny 算法的参数(特别是两个阈值)的选择会显著影响结果,需要根据具体图像进行调整。
第十章:视频处理基础
除了静态图像,OpenCV 也能处理视频和摄像头流。在 OpenCV 中,视频被看作是一系列连续的帧(图像)。
10.1 读取视频或摄像头 (cv2.VideoCapture
)
cv2.VideoCapture()
函数用于打开视频文件或摄像头设备。
“`python
import cv2
打开摄像头:参数为设备索引号,通常 0 代表默认摄像头
cap = cv2.VideoCapture(0)
或者打开视频文件:参数为视频文件路径
cap = cv2.VideoCapture(‘path/to/your/video.mp4’)
检查是否成功打开
if not cap.isOpened():
print(“错误:无法打开摄像头或视频文件。”)
exit()
读取并显示视频帧
while True:
# cap.read() 返回一个布尔值 (是否成功读取) 和一个视频帧 (图像)
ret, frame = cap.read()
# 如果帧读取不成功,可能是视频结束或发生错误
if not ret:
print("无法接收帧 (可能是视频已结束). Exiting ...")
break
# 在这里可以对每一帧进行图像处理
# 例如,将帧转换为灰度图
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 显示原始帧或处理后的帧
cv2.imshow('Original Frame', frame)
cv2.imshow('Grayscale Frame', gray_frame)
# 等待按键 'q' 退出循环
# cv2.waitKey() 的参数决定视频播放速度。例如 25ms 大约是 40fps
# 1ms 用于实时摄像头,或者根据视频帧率调整
if cv2.waitKey(1) == ord('q'): # 按 'q' 键退出
break
释放视频捕获对象并销毁所有窗口
cap.release()
cv2.destroyAllWindows()
“`
cap.isOpened()
: 检查VideoCapture
对象是否成功初始化。cap.read()
: 读取视频的下一帧。如果成功读取,ret
为 True,frame
是该帧的图像;否则ret
为 False,frame
为 None。cv2.waitKey(delay)
: 用于控制播放速度。对于视频,这个延迟决定了每帧显示多久。同时它也监听键盘事件。cap.release()
: 释放视频捕获对象资源。cv2.destroyAllWindows()
: 关闭所有窗口。
10.2 写入视频 (cv2.VideoWriter
)
你可以处理每一帧并将其保存为一个新的视频文件。
“`python
import cv2
打开摄像头或其他视频源
cap = cv2.VideoCapture(0) # 或视频文件路径
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))
获取视频帧率
某些视频源可能返回 0,需要手动指定,例如 20.0 或 30.0
fps = cap.get(cv2.CAP_PROP_FPS)
if fps == 0:
fps = 20.0 # 如果无法获取帧率,设置一个默认值
定义编码器并创建 VideoWriter 对象
FourCC 是一个 4字节码,用于指定视频编码器
例如:’XVID’, ‘MJPG’, ‘DIVX’, ‘X264’ 等
FourCC 的可用性取决于你的系统和安装的编解码器
对于 .avi 文件,常用的有 XVID
对于 .mp4 文件,常用的有 X264 (可能需要安装额外的编解码器)
fourcc = cv2.VideoWriter_fourcc(*’XVID’) # 假设保存为 .avi
out = cv2.VideoWriter(‘output_video.avi’, fourcc, fps, (frame_width, frame_height))
while(cap.isOpened()):
ret, frame = cap.read()
if ret:
# 对帧进行处理 (例如转换为灰度,但为了保存彩色视频,这里直接用 frame)
# gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# out.write(cv2.cvtColor(gray_frame, cv2.COLOR_GRAY2BGR)) # 灰度图保存为3通道
# 写入原始帧到文件
out.write(frame)
# 显示帧
cv2.imshow('frame', frame)
# 按 'q' 退出
if cv2.waitKey(1) == ord('q'):
break
else:
break
释放资源
cap.release()
out.release() # 重要:释放 VideoWriter 对象才能完成写入
cv2.destroyAllWindows()
“`
cv2.VideoWriter_fourcc(*'XXXX')
: 创建 FourCC 代码。'XXXX'
是四字符编码器的字符串。cv2.VideoWriter(filename, fourcc, fps, frameSize)
: 创建 VideoWriter 对象。filename
是输出文件名,fourcc
是编码器,fps
是帧率,frameSize
是帧大小(width, height)
。out.write(frame)
: 写入帧到视频文件。注意,如果写入的帧大小与创建VideoWriter
时指定的大小不符,可能会出错。out.release()
: 释放 VideoWriter 对象,确保所有缓冲的帧都写入文件。
第十一章:总结与展望
恭喜你!通过本文的学习,你已经掌握了使用 OpenCV Python 进行基础图像和视频处理的关键知识和技能,包括:
- 环境搭建和 OpenCV Python 的基本使用。
- 图像的读取、显示和保存。
- 图像的 NumPy 数组表示以及像素和 ROI 的操作。
- 颜色空间的概念与转换。
- 在图像上绘制图形和文本。
- 基本的图像几何变换(缩放、平移、旋转)。
- 图像的阈值化处理(简单阈值和自适应阈值)。
- 图像的平滑处理(均值、高斯、中值滤波)。
- 经典的 Canny 边缘检测算法。
- 简单的视频读取和播放。
这仅仅是计算机视觉和 OpenCV 功能的冰山一角。在你掌握了这些基础知识后,可以进一步探索更多令人兴奋的领域:
- 轮廓 (Contours): 查找和分析图像中的物体轮廓。
- 直方图 (Histograms): 分析图像像素值的分布。
- 形态学操作 (Morphological Operations): 如腐蚀、膨胀、开运算、闭运算,用于图像预处理和分析。
- 特征检测与描述 (Feature Detection and Description): 如 SIFT, SURF, ORB 等,用于图像匹配、物体识别。
- 物体检测 (Object Detection): 使用 Haar Cascade 分类器或更先进的深度学习模型(如 YOLO, SSD)。
- 物体跟踪 (Object Tracking): 在视频序列中跟踪特定物体。
- 相机标定 (Camera Calibration): 校正相机畸变,进行三维测量。
- 图像拼接 (Image Stitching): 将多张图片合成全景图。
- 深度学习推理 (Deep Learning Inference): OpenCV 支持加载和运行各种深度学习模型(如 TensorFlow, PyTorch, ONNX 等训练的模型)。
学习计算机视觉最好的方式是实践。尝试对不同的图片和视频应用你学到的技术,观察效果,并尝试修改参数。查阅 OpenCV 官方文档 (https://docs.opencv.org/) 是深入学习任何功能的最佳途径。
希望这篇详细的基础指南能够帮助你迈出计算机视觉学习的第一步。祝你在探索这个精彩领域时充满乐趣和收获!