Python OpenCV 快速入门 – wiki基地


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 创建虚拟环境的步骤如下:

  1. 打开终端或命令提示符。
  2. 导航到你的项目目录。
  3. 执行命令创建虚拟环境(例如,命名为 myenv):
    bash
    python -m venv myenv
  4. 激活虚拟环境:
    • Windows: myenv\Scripts\activate
    • macOS/Linux: source myenv/bin/activate

激活虚拟环境后,你的终端提示符前会显示虚拟环境的名称(如 (myenv))。现在,我们可以使用 pip 来安装 OpenCV。

安装 OpenCV 的 Python 包(opencv-python):

bash
pip install opencv-python numpy matplotlib

这里我们不仅安装了 opencv-python,还安装了 numpymatplotlibnumpy 是 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

其中 alphabeta 是两张图像的权重,gamma 是一个偏置值。通常设置 alpha + beta = 1gamma = 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() 函数需要输入图像(通常是灰度图像)和两个阈值 threshold1threshold2

“`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(“图像加载失败,跳过边缘检测演示。”)

``
调整
threshold1threshold2可以影响检测到的边缘数量和强度。通常,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 的基础知识。我们学习了:

  1. 如何安装 Python 和 OpenCV 库。
  2. 如何载入、显示和保存图像。
  3. 图像的基本属性和像素操作。
  4. 常见的颜色空间转换。
  5. 在图像上绘制基本图形和文本。
  6. 图像的算术和逻辑运算,以及如何使用掩膜。
  7. 基本的图像处理技术:阈值处理、模糊和边缘检测。
  8. 如何读取、处理和保存视频流。

通过这些基础知识,你现在已经具备了使用 OpenCV 进行简单的图像和视频处理的能力。记住,实践是最好的老师。尝试使用你自己的图片和视频,修改代码,探索不同的参数,看看它们会带来什么效果。

计算机视觉是一个充满挑战和机遇的领域。掌握 OpenCV,将为你打开通往这个精彩世界的大门。祝你在 OpenCV 的学习和实践中取得成功!

发表评论

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

滚动至顶部