Python OpenCV 入门教程 – wiki基地


Python OpenCV 入门教程:从零开始掌握计算机视觉基础

前言

计算机视觉(Computer Vision)是人工智能领域中一个充满活力和挑战性的分支,它旨在让计算机能够“看懂”图像和视频,并从中提取有用的信息。从自动驾驶到医疗影像分析,从人脸识别到工业缺陷检测,计算机视觉的应用无处不在。

而在众多的计算机视觉库中,OpenCV(Open Source Computer Vision Library)无疑是最著名、功能最强大的一个。它是一个开源的跨平台计算机视觉库,提供了C++、Python、Java等多种语言的接口,其中 Python 接口因其简洁易用而受到广大开发者和研究人员的青睐。

本教程将带领你从零开始,使用 Python 和 OpenCV 掌握计算机视觉的基础知识和常用操作。无论你是学生、工程师,还是仅仅对计算机视觉感到好奇,这篇教程都将是你开启OpenCV学习之旅的绝佳起点。

准备工作:安装 OpenCV

在开始之前,你需要确保你的系统中已经安装了 Python。推荐使用 Python 3.6 或更高版本。

安装 OpenCV Python 包非常简单,使用 pip 包管理器即可完成。打开你的终端或命令提示符,然后输入以下命令:

bash
pip install opencv-python

如果你需要额外的贡献模块(例如一些非自由算法或实验性功能),你可以安装 opencv-contrib-python

bash
pip install opencv-contrib-python

对于本入门教程,安装 opencv-python 就足够了。

安装完成后,你可以通过在 Python 解释器中导入 cv2 模块来验证是否安装成功:

python
import cv2
print(cv2.__version__)

如果没有报错并成功打印出 OpenCV 的版本号,恭喜你,你已经准备好进入 OpenCV 的世界了!

第一章:初识 OpenCV – 加载、显示和保存图像

一切从最基础的操作开始:加载一张图片,在窗口中显示它,然后可能将其保存到另一个文件。

在 OpenCV 中,图像被表示为多维 NumPy 数组。对于彩色图像,它通常是一个三维数组,形状为 (height, width, channels),其中 channels 通常代表 BGR(蓝、绿、红)三个颜色通道。对于灰度图像,它是一个二维数组,形状为 (height, width)。每个像素的值通常是 8 位无符号整数(uint8),范围在 0 到 255 之间。

让我们来看第一个代码示例:

“`python
import cv2
import numpy as np

定义图片路径,请替换为你自己的图片文件路径

确保图片文件存在于你的运行目录下,或者提供绝对路径

image_path = ‘your_image.jpg’ # 例如:’lena.jpg’ 或 ‘test.png’

1. 加载图像

cv2.imread() 函数用于加载图像

参数1:图像文件的路径

参数2:加载图像的方式

cv2.IMREAD_COLOR: 加载彩色图像(默认值),忽略透明度

cv2.IMREAD_GRAYSCALE: 加载灰度图像

cv2.IMREAD_UNCHANGED: 加载原始图像,包括透明度通道

img = cv2.imread(image_path, cv2.IMREAD_COLOR)

检查图像是否成功加载

if img is None:
print(f”错误:无法加载图像文件 ‘{image_path}’。请检查文件路径是否正确。”)
else:
print(f”图像 ‘{image_path}’ 加载成功!”)

# 2. 显示图像
# cv2.imshow() 函数用于在窗口中显示图像
# 参数1:窗口的名称(字符串)
# 参数2:要显示的图像 NumPy 数组
cv2.imshow('Original Image', img)

# 3. 等待按键
# cv2.waitKey() 函数等待键盘事件
# 参数:等待的毫秒数。
#   如果参数是 0,则无限等待直到有键按下。
#   如果参数是正数 N,则等待 N 毫秒。如果在 N 毫秒内没有键按下,则返回 -1。
# 返回值是按键的 ASCII 码。
print("按下任意键关闭图像窗口...")
cv2.waitKey(0) # 0 表示无限等待直到按键

# 4. 销毁所有窗口
# cv2.destroyAllWindows() 函数用于销毁所有 OpenCV 创建的窗口
cv2.destroyAllWindows()

# 5. 保存图像
# cv2.imwrite() 函数用于将图像保存到文件
# 参数1:保存文件的路径(包括文件名和扩展名)
# 参数2:要保存的图像 NumPy 数组
# 保存的格式由文件扩展名决定(如 .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}'")

“`

代码解释:

  1. import cv2: 导入 OpenCV 库。
  2. cv2.imread(image_path, cv2.IMREAD_COLOR): 尝试从指定路径加载彩色图像。如果文件不存在或路径错误,它将返回 None。因此,检查返回值是否为 None 是一个好习惯。
  3. cv2.imshow('Original Image', img): 创建一个名为 ‘Original Image’ 的窗口,并在其中显示加载的图像 img
  4. cv2.waitKey(0): 这行代码是显示图像的关键。它会暂停程序执行,直到用户按下键盘上的任意键。如果没有这行,窗口会一闪而过,因为程序会立即执行到最后并退出。参数 0 表示无限等待。
  5. cv2.destroyAllWindows(): 在用户按键后,这行代码会关闭所有 OpenCV 创建的窗口。
  6. cv2.imwrite('saved_image.png', img): 将图像 img 保存为名为 ‘saved_image.png’ 的 PNG 文件。文件格式由扩展名 .png 决定。

运行此代码,你需要准备一张图片并将其路径替换到 image_path 变量中。

第二章:图像的基本属性与像素操作

在 OpenCV 中,图像被当作 NumPy 数组处理,这意味着我们可以利用 NumPy 的强大功能来访问和修改图像的像素值,以及获取图像的各种属性。

2.1 获取图像属性

你可以通过 NumPy 数组的属性来获取图像的关键信息:

  • img.shape: 返回一个元组,表示图像的维度。对于彩色图像,它是 (height, width, channels);对于灰度图像,它是 (height, width)
  • img.size: 返回图像的总像素数。对于彩色图像,是 height * width * channels;对于灰度图像,是 height * width
  • img.dtype: 返回图像中像素的数据类型,通常是 uint8 (8位无符号整数)。

“`python
import cv2
import numpy as np

image_path = ‘your_image.jpg’
img = cv2.imread(image_path, cv2.IMREAD_COLOR)

if img is not None:
# 获取并打印图像属性
print(“图像的形状 (高, 宽, 通道):”, img.shape)
print(“图像的总像素数:”, img.size)
print(“图像的数据类型:”, img.dtype)

# 如果是彩色图像,获取通道数
if len(img.shape) == 3:
    height, width, channels = img.shape
    print("图像高度:", height)
    print("图像宽度:", width)
    print("图像通道数:", channels)
else: # 灰度图像
    height, width = img.shape
    print("图像高度:", height)
    print("图像宽度:", width)
    print("图像是灰度图")

else:
print(f”错误:无法加载图像文件 ‘{image_path}’。”)

“`

2.2 访问和修改像素值

由于图像是 NumPy 数组,你可以像访问普通数组元素一样访问和修改像素。记住,对于彩色图像,通道顺序是 BGR。

  • 访问单个像素: img[row, col] (灰度图) 或 img[row, col, channel] (彩色图)。例如,img[10, 50] 返回位于第 10 行、第 50 列的像素值。如果是彩色图,返回值是一个包含 BGR 三个值的列表或数组(如 [150, 120, 80])。img[10, 50, 0] 返回该像素的蓝色通道值。
  • 修改单个像素: img[row, col] = new_value。例如,img[10, 50] = [255, 0, 0] 将第 10 行、第 50 列的像素设置为纯蓝色。
  • 访问和修改区域 (ROI – Region of Interest): 利用 NumPy 的切片(slicing)功能。img[y1:y2, x1:x2] 可以选择一个矩形区域。

“`python
import cv2
import numpy as np

image_path = ‘your_image.jpg’ # 确保是彩色图像
img = cv2.imread(image_path, cv2.IMREAD_COLOR)

if img is not None:
# 访问单个像素(例如:第100行,第150列的像素)
# 注意:行对应高度,列对应宽度
px = img[100, 150]
print(“像素 (100, 150) 的 BGR 值:”, px)

# 访问单个通道的值(例如:第100行,第150列像素的蓝色通道值)
blue_value = img[100, 150, 0]
print("像素 (100, 150) 的蓝色通道值:", blue_value)

# 修改单个像素值(例如:将第100行,第150列的像素设置为红色)
img[100, 150] = [0, 0, 255] # [B, G, R]

# 访问并修改图像的某个区域 (ROI)
# 例如:提取图像左上角 100x100 的区域
roi = img[0:100, 0:100]
print("ROI 区域的形状:", roi.shape)

# 将 ROI 区域设置为绿色
img[0:100, 0:100] = [0, 255, 0] # [B, G, R]

# 显示修改后的图像
cv2.imshow('Modified Image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

else:
print(f”错误:无法加载图像文件 ‘{image_path}’。”)

“`

重要提示: 在访问和修改像素时,要确保你使用的行和列索引在图像的有效范围内(即 0 到 height-1 和 0 到 width-1)。超出范围会导致索引错误。

第三章:图像的基本操作 – 缩放、裁剪、旋转和翻转

OpenCV 提供了一些非常方便的函数来进行图像的基本几何变换。

3.1 缩放图像

cv2.resize() 函数用于改变图像的大小。

“`python
import cv2
import numpy as np

image_path = ‘your_image.jpg’
img = cv2.imread(image_path, cv2.IMREAD_COLOR)

if img is not None:
# 获取原始图像的尺寸
height, width = img.shape[:2]

# 按照固定尺寸缩放 (例如:200x300)
resized_img_fixed = cv2.resize(img, (200, 300)) # 注意:尺寸是 (宽度, 高度)

# 按照比例因子缩放 (例如:宽度和高度都缩小一半)
scale_factor = 0.5
resized_img_scale = cv2.resize(img, None, fx=scale_factor, fy=scale_factor)

# 按照比例因子放大 (例如:宽度和高度都放大2倍)
scale_factor_large = 2.0
resized_img_large = cv2.resize(img, None, fx=scale_factor_large, fy=scale_factor_large)

# 缩放时的插值方法 (interpolation)
# cv2.INTER_AREA: 主要用于缩小图像,可以避免波纹出现
# cv2.INTER_CUBIC: 4x4像素邻域的立方插值,效果较好,计算量较大
# cv2.INTER_LINEAR: 双线性插值(默认),用于放大图像
# cv2.INTER_NEAREST: 最近邻插值,速度最快,但效果最差

# 示例:使用 AREA 插值缩小
resized_img_area = cv2.resize(img, (width // 2, height // 2), interpolation=cv2.INTER_AREA)

cv2.imshow('Original Image', img)
cv2.imshow('Resized Fixed (200x300)', resized_img_fixed)
cv2.imshow('Resized Scale (0.5x)', resized_img_scale)
cv2.imshow('Resized Scale (2.0x)', resized_img_large)
cv2.imshow('Resized Area (Half Size)', resized_img_area)

cv2.waitKey(0)
cv2.destroyAllWindows()

else:
print(f”错误:无法加载图像文件 ‘{image_path}’。”)

“`

注意: cv2.resize() 的尺寸参数是一个元组 (width, height),这与 img.shape 返回的 (height, width, channels) 是相反的顺序。

3.2 裁剪图像

裁剪图像本质上就是提取图像的 ROI,使用 NumPy 的切片功能即可实现。

“`python
import cv2
import numpy as np

image_path = ‘your_image.jpg’
img = cv2.imread(image_path, cv2.IMREAD_COLOR)

if img is not None:
# 裁剪图像:从 (100, 150) 位置开始,提取一个 200×300 (高x宽) 的区域
# 注意:切片是 [start_row:end_row, start_col:end_col]
# 对应像素坐标 (x, y) 的话,是 img[y1:y2, x1:x2]
x1, y1 = 150, 100 # 起始点坐标 (x, y)
x2, y2 = x1 + 300, y1 + 200 # 结束点坐标 (x, y)

# 检查裁剪区域是否超出图像范围
height, width = img.shape[:2]
if y2 > height or x2 > width:
    print("裁剪区域超出图像范围!")
    # 可以选择调整裁剪区域或者跳过裁剪
    cropped_img = img # 这里简单地保留原图
else:
    cropped_img = img[y1:y2, x1:x2]
    print("图像裁剪成功。")

cv2.imshow('Original Image', img)
cv2.imshow('Cropped Image', cropped_img)

cv2.waitKey(0)
cv2.destroyAllWindows()

else:
print(f”错误:无法加载图像文件 ‘{image_path}’。”)
“`

3.3 旋转图像

旋转图像稍微复杂一些,需要先计算旋转矩阵,然后应用仿射变换。

“`python
import cv2
import numpy as np

image_path = ‘your_image.jpg’
img = cv2.imread(image_path, cv2.IMREAD_COLOR)

if img is not None:
height, width = img.shape[:2]

# 1. 定义旋转中心 (通常是图像中心)、角度和缩放比例
center = (width // 2, height // 2)
angle = 45 # 旋转角度,正数表示逆时针旋转
scale = 1.0 # 缩放比例

# 2. 获取旋转矩阵
# cv2.getRotationMatrix2D(center, angle, scale)
# center: 旋转中心
# angle: 旋转角度
# scale: 缩放比例
rotation_matrix = cv2.getRotationMatrix2D(center, angle, scale)

# 为了完整显示旋转后的图像(不被裁剪),需要计算新的边界尺寸
# 这里省略计算过程,直接使用原图尺寸,旋转后的图像可能会被部分裁剪
# 如果需要完整显示,需要更复杂的计算和warpAffine参数调整

# 3. 应用仿射变换进行旋转
# cv2.warpAffine(src, M, dsize, flags, borderMode, borderValue)
# src: 输入图像
# M: 2x3 的变换矩阵 (这里是旋转矩阵)
# dsize: 输出图像的大小 (宽度, 高度)
# flags: 插值方法,默认为 cv2.INTER_LINEAR
# borderMode: 边界模式,默认为 cv2.BORDER_CONSTANT
# borderValue: 边界填充值,默认为 0 (黑色)
rotated_img = cv2.warpAffine(img, rotation_matrix, (width, height))

cv2.imshow('Original Image', img)
cv2.imshow(f'Rotated {angle} Degrees', rotated_img)

cv2.waitKey(0)
cv2.destroyAllWindows()

else:
print(f”错误:无法加载图像文件 ‘{image_path}’。”)

“`

3.4 翻转图像

cv2.flip() 函数可以轻松实现图像的水平或垂直翻转。

“`python
import cv2
import numpy as np

image_path = ‘your_image.jpg’
img = cv2.imread(image_path, cv2.IMREAD_COLOR)

if img is not None:
# 翻转图像
# cv2.flip(src, flipCode)
# src: 输入图像
# flipCode: 翻转模式
# 0: 沿 X 轴翻转 (垂直翻转)
# 正数 (例如 1): 沿 Y 轴翻转 (水平翻转)
# 负数 (例如 -1): 沿 X 轴和 Y 轴同时翻转 (水平+垂直)

flipped_vertical = cv2.flip(img, 0) # 垂直翻转
flipped_horizontal = cv2.flip(img, 1) # 水平翻转
flipped_both = cv2.flip(img, -1) # 水平+垂直翻转

cv2.imshow('Original Image', img)
cv2.imshow('Flipped Vertically', flipped_vertical)
cv2.imshow('Flipped Horizontally', flipped_horizontal)
cv2.imshow('Flipped Both', flipped_both)

cv2.waitKey(0)
cv2.destroyAllWindows()

else:
print(f”错误:无法加载图像文件 ‘{image_path}’。”)
“`

第四章:在图像上绘图

OpenCV 提供了各种绘图函数,可以在图像上绘制线条、矩形、圆形等多边形,以及添加文本。这对于标注图像、创建简单的可视化界面或生成图像数据非常有用。

绘图通常在一个空白的图像或者已有图像的副本上进行。记住,绘图操作会直接修改输入的图像数组。

“`python
import cv2
import numpy as np

创建一个空白的彩色图像 (宽度 600, 高度 400)

cv2.COLOR_BGR2GRAY 是灰度,这里需要彩色

np.zeros((height, width, channels), dtype)

blank_image = np.zeros((400, 600, 3), dtype=np.uint8) # 黑色背景

定义颜色 (BGR 格式)

blue = (255, 0, 0)
green = (0, 255, 0)
red = (0, 0, 255)
yellow = (0, 255, 255)
white = (255, 255, 255)

1. 绘制直线

cv2.line(img, pt1, pt2, color, thickness)

img: 要绘图的图像

pt1, pt2: 直线起止点的坐标 (x, y)

color: 直线颜色 (BGR)

thickness: 直线粗细 (像素)

cv2.line(blank_image, (50, 50), (200, 50), blue, 5) # 水平线
cv2.line(blank_image, (50, 50), (50, 200), green, 3) # 垂直线
cv2.line(blank_image, (50, 50), (200, 200), red, 2) # 对角线

2. 绘制矩形

cv2.rectangle(img, pt1, pt2, color, thickness)

pt1: 矩形左上角坐标 (x, y)

pt2: 矩形右下角坐标 (x, y)

thickness: 边框粗细。如果为 -1,则填充矩形。

cv2.rectangle(blank_image, (250, 50), (400, 200), yellow, -1) # 填充的矩形
cv2.rectangle(blank_image, (450, 50), (550, 200), white, 2) # 边框矩形

3. 绘制圆形

cv2.circle(img, center, radius, color, thickness)

center: 圆心坐标 (x, y)

radius: 圆的半径

cv2.circle(blank_image, (325, 300), 50, blue, -1) # 填充的圆形
cv2.circle(blank_image, (500, 300), 40, green, 3) # 边框圆形

4. 绘制多边形

cv2.polylines(img, pts, isClosed, color, thickness)

pts: 多边形顶点的数组。需要是一个 np.array,形状为 (顶点数, 1, 2)。

isClosed: 如果为 True,则连接最后一个点和第一个点形成闭合多边形。

pts = np.array([[10, 300], [100, 250], [200, 300], [100, 350]], np.int32)

需要将顶点数组 reshape 成 OpenCV polylines 函数需要的格式

pts = pts.reshape((-1, 1, 2))
cv2.polylines(blank_image, [pts], True, red, 5)

5. 添加文本

cv2.putText(img, text, org, fontFace, fontScale, color, thickness, lineType)

text: 要添加的文本字符串

org: 文本左下角坐标 (x, y)

fontFace: 字体类型 (例如 cv2.FONT_HERSHEY_SIMPLEX, cv2.FONT_HERSHEY_PLAIN 等)

fontScale: 字体缩放比例

lineType: 线条类型 (例如 cv2.LINE_AA 抗锯齿)

font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(blank_image, ‘Hello OpenCV!’, (50, 380), font, 1, white, 2, cv2.LINE_AA)

显示绘图结果

cv2.imshow(‘Drawing Demo’, blank_image)

cv2.waitKey(0)
cv2.destroyAllWindows()

“`

代码解释:

  • np.zeros(...): 创建一个指定大小、通道数和数据类型的数组,所有元素初始化为 0,通常用来创建黑色背景图像。
  • 绘图函数的坐标系原点是图像的左上角 (0, 0)。X 坐标向右增加,Y 坐标向下增加。
  • 颜色是 BGR 顺序的元组 (Blue, Green, Red),每个分量的值在 0-255 之间。
  • thickness 参数控制线条的粗细。对于矩形、圆形等形状,thickness=-1 表示填充整个形状。
  • cv2.polylines 需要的顶点数组格式比较特殊,需要进行 reshape。

第五章:处理视频 – 读取、显示和保存视频

除了处理静态图像,OpenCV 强大的功能还体现在视频处理上。视频可以看作是一系列连续的图像帧。OpenCV 可以从文件或摄像头读取视频流,逐帧处理,然后显示或保存处理后的视频。

5.1 从文件或摄像头读取视频

cv2.VideoCapture 对象用于读取视频流。

“`python
import cv2

创建 VideoCapture 对象

参数可以是视频文件路径 (字符串) 或 摄像头索引 (整数)

0 表示默认摄像头,1 表示第二个摄像头,依此类推

cap = cv2.VideoCapture(0) # 尝试打开默认摄像头

或者 cap = cv2.VideoCapture(‘your_video.mp4’) # 尝试打开视频文件

检查 VideoCapture 是否成功打开

if not cap.isOpened():
print(“错误:无法打开视频源。请检查文件路径或摄像头索引。”)
else:
print(“视频源已成功打开。”)

# 获取视频的一些属性 (如果从文件读取)
# 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}, 视频高度: {frame_height}, 帧率: {fps}")


# 循环读取视频帧
while True:
    # cap.read() 读取一帧
    # ret: 布尔值,表示是否成功读取帧 (True/False)
    # frame: 读取到的图像帧 (NumPy 数组)
    ret, frame = cap.read()

    # 如果成功读取帧 (ret 为 True)
    if ret:
        # 在这里可以对每一帧进行图像处理
        # 例如,将帧转换为灰度图
        gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        # 显示原始帧和灰度帧
        cv2.imshow('Original Frame', frame)
        cv2.imshow('Gray Frame', gray_frame)

        # 等待按键
        # cv2.waitKey() 在视频循环中通常设置为一个小的值 (例如 1 或 25)
        # 这样可以在显示帧的同时响应按键,又不至于让视频卡顿
        # 按下 'q' 键时退出循环 (ord('q') 获取字符 'q' 的 ASCII 值)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    else:
        # 如果 ret 为 False,说明视频已经结束或读取出错
        print("视频读取结束或出错。")
        break

# 释放 VideoCapture 对象
cap.release()
# 销毁所有窗口
cv2.destroyAllWindows()

“`

代码解释:

  • cv2.VideoCapture(0): 打开默认摄像头。如果你有多个摄像头,可以尝试 1, 2, …。如果想打开视频文件,传入文件路径字符串。
  • cap.isOpened(): 检查 VideoCapture 对象是否成功初始化。
  • cap.read(): 读取视频的下一帧。它返回一个元组 (ret, frame)ret 是一个布尔值,表示是否成功读取帧;frame 是读取到的图像帧(一个 NumPy 数组)。
  • while True:: 循环读取每一帧直到视频结束或用户退出。
  • if cv2.waitKey(1) & 0xFF == ord('q'):: 在视频循环中,waitKey() 的参数通常是一个小的正数(例如 1 或 25,取决于你希望的帧率和响应速度)。这允许程序在显示帧之间短暂暂停,同时监听键盘事件。& 0xFF == ord('q') 是一种跨平台的检查特定按键(这里是 ‘q’)的方式。
  • cap.release(): 释放 VideoCapture 对象占用的资源。
  • cv2.destroyAllWindows(): 关闭所有窗口。

5.2 保存视频

要保存视频,你需要创建一个 cv2.VideoWriter 对象。

“`python
import cv2
import numpy as np

创建 VideoCapture 对象 (例如从摄像头读取)

cap = cv2.VideoCapture(0)

if not cap.isOpened():
print(“错误:无法打开视频源。”)
else:
print(“视频源已成功打开。”)

# 获取帧的宽度和高度 (必须是整数)
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# 获取或定义帧率 (例如 20 fps)
# fps = cap.get(cv2.CAP_PROP_FPS) # 从原视频获取
fps = 20 # 自己定义

# 定义编解码器 (FOURCC - Four Character Code)
# 不同编解码器有不同的兼容性和压缩效果
# 对于 .avi 文件,常用 'XVID' 或 'MJPG'
# 对于 .mp4 文件,常用 'MP4V' (MPEG-4) 或 'X264' (H.264, 可能需要额外安装库)
# 这里使用 XVID,它通常比较通用
fourcc = cv2.VideoWriter_fourcc(*'XVID')

# 创建 VideoWriter 对象
# cv2.VideoWriter(filename, fourcc, fps, frameSize[, isColor])
# filename: 保存的视频文件路径
# fourcc: 编解码器
# fps: 每秒帧数
# frameSize: 帧的尺寸 (宽度, 高度)
# isColor: 如果为 True,则编码彩色帧 (默认)。如果为 False,则编码灰度帧。
out = cv2.VideoWriter('output.avi', fourcc, fps, (frame_width, frame_height))
# 如果要保存灰度视频,isColor=False,且 frameSize 只需要 (width, height),不需要通道数
# out_gray = cv2.VideoWriter('output_gray.avi', fourcc, fps, (frame_width, frame_height), isColor=False)


print(f"视频保存器已创建,将保存到 'output.avi' (FOURCC: XVID, FPS: {fps}, Size: {frame_width}x{frame_height})")


while True:
    ret, frame = cap.read()

    if ret:
        # 显示原始帧
        cv2.imshow('Original Frame', frame)

        # 在这里可以对 frame 进行处理 (例如:翻转、添加文字等)
        # processed_frame = cv2.flip(frame, 1)

        # 将处理后的帧写入视频文件
        # out.write(processed_frame)
        out.write(frame) # 这里直接写入原始帧

        # 如果你需要保存灰度视频,并且 VideoWriter 是以 isColor=False 创建的
        # gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        # out_gray.write(gray_frame)


        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    else:
        break

# 释放资源
cap.release()
out.release() # 释放 VideoWriter 对象
# out_gray.release() # 如果创建了灰度 writer 也要释放
cv2.destroyAllWindows()

print("视频录制和保存已完成。")

“`

代码解释:

  • cap.get(cv2.CAP_PROP_FRAME_WIDTH) 等:用于获取视频属性。
  • cv2.VideoWriter_fourcc(*'XVID'): 定义视频的编解码器。* 符号用于解包字符串 ‘XVID’ 为单独的字符参数 ‘X’, ‘V’, ‘I’, ‘D’,这是该函数的要求。请注意,编解码器的可用性取决于你的系统安装情况。
  • cv2.VideoWriter(...): 创建一个 VideoWriter 对象,指定输出文件名、编解码器、帧率和帧尺寸。
  • out.write(frame): 将当前的图像帧写入视频文件。请确保写入的帧的尺寸和通道数与 VideoWriter 初始化时指定的一致。如果 VideoWriter 是彩色模式,你不能直接写入灰度帧;你需要先将灰度帧转换回 BGR 格式(例如使用 cv2.cvtColor(gray_frame, cv2.COLOR_GRAY2BGR)),但这通常会得到一个三通道的灰度图像,而不是原始彩色图。更好的做法是,如果你想保存灰度视频,初始化 VideoWriter 时设置 isColor=False 并直接写入灰度帧。
  • out.release(): 在循环结束后,释放 VideoWriter 对象,确保所有缓冲的帧都被写入文件。这步非常重要,否则文件可能损坏或无法播放。

第六章:简单图像处理技术 – 灰度转换与阈值分割

OpenCV 提供了大量的图像处理函数。这里我们介绍两个最基础且常用的操作:灰度转换和阈值分割。

6.1 灰度转换

将彩色图像转换为灰度图像是很多计算机视觉任务的预处理步骤。这可以减少数据量,同时保留图像的大部分结构信息。

cv2.cvtColor() 函数用于在不同的颜色空间之间进行转换。

“`python
import cv2
import numpy as np

image_path = ‘your_image.jpg’ # 确保是彩色图像
img = cv2.imread(image_path, cv2.IMREAD_COLOR)

if img is not None:
# 将彩色图像转换为灰度图像
# cv2.cvtColor(src, code)
# src: 输入图像
# code: 转换的代码,例如 cv2.COLOR_BGR2GRAY (将 BGR 转换为灰度)
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 将灰度图像转换回 BGR (每个通道的值都一样)
# gray_to_bgr = cv2.cvtColor(gray_img, cv2.COLOR_GRAY2BGR)


cv2.imshow('Original Image', img)
cv2.imshow('Grayscale Image', gray_img)
# cv2.imshow('Gray to BGR Image', gray_to_bgr) # 演示灰度转BGR

cv2.waitKey(0)
cv2.destroyAllWindows()

else:
print(f”错误:无法加载图像文件 ‘{image_path}’。”)

“`

代码解释:

  • cv2.COLOR_BGR2GRAY: 这是将 BGR 彩色图像转换为灰度图像的常量。OpenCV 支持大量的颜色空间转换,例如 cv2.COLOR_BGR2HSV (转换为 HSV 色彩空间,用于颜色分割等任务)。

6.2 阈值分割 (Thresholding)

阈值分割是一种简单的图像二值化技术,它根据像素值与一个阈值的比较结果,将图像分割成两个区域(通常是前景和背景),通常用于从背景中分离出物体。

cv2.threshold() 函数用于执行阈值分割。

“`python
import cv2
import numpy as np

image_path = ‘your_image.jpg’ # 可以是彩色或灰度图,阈值化通常在灰度图上进行
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) # 直接加载为灰度图

if img is not None:
# 应用简单的全局阈值分割
# cv2.threshold(src, thresh, maxval, type)
# src: 输入图像 (通常是灰度图)
# thresh: 阈值
# maxval: 当像素值高于(或低于,取决于类型)阈值时赋予的新值
# type: 阈值处理的类型
# cv2.THRESH_BINARY: pix > thresh ? maxval : 0
# cv2.THRESH_BINARY_INV: pix > thresh ? 0 : maxval
# cv2.THRESH_TRUNC: pix > thresh ? thresh : pix
# cv2.THRESH_TOZERO: pix > thresh ? pix : 0
# cv2.THRESH_TOZERO_INV: pix > thresh ? 0 : pix

# 返回值:
# ret: 实际使用的阈值 (对于简单的阈值类型,就是输入的 thresh)
# dst: 阈值处理后的图像
ret, thresh_binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
ret, thresh_binary_inv = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
ret, thresh_trunc = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)
ret, thresh_tozero = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)
ret, thresh_tozero_inv = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)


# 演示效果
cv2.imshow('Original Grayscale', img)
cv2.imshow('THRESH_BINARY (thresh=127)', thresh_binary)
cv2.imshow('THRESH_BINARY_INV (thresh=127)', thresh_binary_inv)
cv2.imshow('THRESH_TRUNC (thresh=127)', thresh_trunc)
cv2.imshow('THRESH_TOZERO (thresh=127)', thresh_tozero)
cv2.imshow('THRESH_TOZERO_INV (thresh=127)', thresh_tozero_inv)


cv2.waitKey(0)
cv2.destroyAllWindows()

else:
print(f”错误:无法加载图像文件 ‘{image_path}’。”)

“`

代码解释:

  • cv2.threshold() 函数返回两个值:实际使用的阈值和阈值处理后的图像。
  • thresh 是你设定的阈值。maxval 是像素值超过(或低于,取决于类型)阈值时被设置为的值。
  • cv2.THRESH_BINARY 是最常用的类型,它将所有高于阈值的像素设置为 maxval,所有低于阈值的像素设置为 0。
  • 阈值分割的效果很大程度上取决于选择的阈值 thresh。对于背景和前景亮度对比明显的图像,手动选择一个阈值效果可能不错。对于光照不均的图像,可能需要使用自适应阈值(cv2.adaptiveThreshold()),这将在后续教程中介绍。

6.3 简单模糊 (Simple Blurring)

模糊是一种平滑图像、去除噪声的常用技术。OpenCV 提供了几种模糊方法,其中最简单的是平均模糊和高斯模糊。

这里以高斯模糊为例:

“`python
import cv2
import numpy as np

image_path = ‘your_image.jpg’
img = cv2.imread(image_path, cv2.IMREAD_COLOR)

if img is not None:
# 应用高斯模糊
# cv2.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]])
# src: 输入图像
# ksize: 高斯核的大小 (宽度, 高度)。必须是正奇数元组。
# sigmaX: 高斯核在 X 方向的标准差。如果为 0,则根据 ksize 计算。
# sigmaY: 高斯核在 Y 方向的标准差。如果为 0,则与 sigmaX 相同。
# 通常将 sigmaX 和 sigmaY 设置为 0,让函数根据 ksize 计算。
# ksize 越大,模糊程度越高。
blurred_img_small = cv2.GaussianBlur(img, (5, 5), 0) # 较小的高斯核
blurred_img_large = cv2.GaussianBlur(img, (15, 15), 0) # 较大的高斯核

# 也可以将图像转换为灰度再模糊
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred_gray = cv2.GaussianBlur(gray_img, (9, 9), 0)

cv2.imshow('Original Image', img)
cv2.imshow('Blurred Image (ksize=5x5)', blurred_img_small)
cv2.imshow('Blurred Image (ksize=15x15)', blurred_img_large)
cv2.imshow('Blurred Grayscale (ksize=9x9)', blurred_gray)


cv2.waitKey(0)
cv2.destroyAllWindows()

else:
print(f”错误:无法加载图像文件 ‘{image_path}’。”)

“`

代码解释:

  • cv2.GaussianBlur(): 使用高斯滤波器对图像进行模糊。
  • ksize: 决定了滤波器的大小,必须是正奇数。例如 (5, 5) 表示 5×5 的高斯核。
  • sigmaX, sigmaY: 控制高斯分布的标准差,也就是模糊的程度。通常设置为 0,让 OpenCV 根据 ksize 自动计算。

模糊操作是许多图像处理流水线的重要组成部分,常用于降噪或在进行边缘检测、特征提取之前平滑图像。

总结与展望

恭喜你!通过本教程的学习,你已经掌握了使用 Python 和 OpenCV 进行计算机视觉编程的基础:

  • 安装和导入 OpenCV 库。
  • 加载、显示和保存图像。
  • 理解图像作为 NumPy 数组的表示,并进行像素级别的访问和修改。
  • 执行图像的基本几何变换:缩放、裁剪、旋转和翻转。
  • 在图像上绘制基本形状和文本。
  • 从文件或摄像头读取、显示和保存视频。
  • 应用基本的图像处理技术:灰度转换、阈值分割和高斯模糊。

这仅仅是 OpenCV 强大功能的冰山一角。计算机视觉是一个广阔的领域,OpenCV 提供了更多高级的功能等待你去探索,例如:

  • 图像滤波和边缘检测: Sobel, Canny 等。
  • 形态学操作: 腐蚀、膨胀、开闭运算等。
  • 直方图: 计算、绘制和分析图像的颜色分布。
  • 轮廓检测和处理: 找到图像中的连通区域。
  • 特征检测和匹配: SIFT, SURF, ORB 等算法。
  • 目标检测和跟踪: Haar Cascades, 深度学习模型(如 YOLO, SSD)的接口。
  • 相机标定和立体视觉: 用于三维重建和测量。
  • 机器学习模块: 内置了一些机器学习算法。

下一步你可以:

  1. 实践: 尝试修改本教程中的代码,使用不同的图片或参数,观察效果。
  2. 深入学习: 查阅 OpenCV 官方文档,学习更多函数的用法。
  3. 探索进阶主题: 学习边缘检测、轮廓处理、目标检测等高级主题。
  4. 结合其他库: 将 OpenCV 与 NumPy、Matplotlib、Scikit-image 等其他 Python 库结合使用,解决更复杂的计算机视觉问题。

计算机视觉的世界充满了可能性。希望本教程为你打开了这扇大门,并激发了你继续深入学习的热情。不断实践,不断探索,你将能够利用 OpenCV 创造出令人惊叹的应用!

祝你在计算机视觉的学习之旅中取得成功!


发表评论

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

滚动至顶部