精通计算机视觉:OpenCV Python 深度学习教程(超详细版)
计算机视觉(Computer Vision)是一门研究如何使计算机“看”并理解图像和视频的科学。它在自动驾驶、人脸识别、医疗影像分析、工业自动化等领域有着广泛的应用。而 OpenCV(Open Source Computer Vision Library)是目前最流行、功能最强大的计算机视觉库之一。结合 Python 语言的易用性和强大的生态系统,学习 OpenCV Python 成为了进入计算机视觉领域的绝佳途径。
本文将带你深入了解 OpenCV Python 的世界,从基础的图像操作到复杂的图像处理技术,为你提供一个全面的学习指南。
1. 初识 OpenCV 与 Python
1.1 什么是 OpenCV?
OpenCV 是一个高度优化的 C++ 库,提供了一系列用于图像处理、计算机视觉和机器学习的算法。它最初由 Intel 开发,现在由 Willow Garage 和 Itseez 等公司维护,并得到了社区的广泛支持。OpenCV 具有跨平台性,支持 Windows、Linux、macOS、Android、iOS 等多个操作系统。
1.2 为什么选择 OpenCV Python?
- 易用性: Python 语法简洁明了,学习曲线平缓,使得开发者能够快速原型开发和迭代。
- 强大的生态: Python 拥有 NumPy(用于数值计算)、Matplotlib(用于数据可视化)、Scikit-learn(用于机器学习)等强大的库,这些库与 OpenCV 可以无缝集成,极大地扩展了计算机视觉应用的开发能力。
- 快速开发: 尽管 OpenCV 的核心算法是 C++ 实现的,但在 Python 中的调用效率通常足够满足大多数应用的需求。对于性能要求极高的部分,可以使用 C++ 实现并通过 Python 绑定调用。
- 社区活跃: OpenCV Python 社区非常活跃,有大量的文档、教程和示例代码可供参考。
1.3 应用领域概览
OpenCV 被广泛应用于:
- 图像/视频分析(加载、保存、显示、编辑)
- 物体检测与识别(人脸、车辆、特定物体)
- 目标跟踪
- 姿态估计
- 三维重建
- 图像拼接
- 运动分析
- 机器学习(部分传统算法)
2. 环境搭建:准备你的开发环境
在开始之前,你需要安装 Python 和 OpenCV 库。
2.1 安装 Python
首先,确保你的系统中安装了 Python。推荐安装 Python 3.6 或更高版本。你可以从 Python 官方网站 (https://www.python.org/downloads/) 下载安装包。
2.2 安装 OpenCV
安装 OpenCV Python 库非常简单,使用 pip 包管理器即可:
bash
pip install opencv-python
如果你需要额外的模块(如 SIFT, SURF 等专利算法,虽然在新版本中已集成部分),可以安装 opencv-contrib-python
:
bash
pip install opencv-contrib-python
注意: 通常情况下,opencv-python
就已经足够满足大部分需求。
2.3 验证安装
打开 Python 解释器或创建一个新的 Python 脚本,输入以下代码:
python
import cv2
print(cv2.__version__)
如果能够成功导入 cv2
并打印出版本号,说明安装成功。
3. 图像基础:加载、显示与保存
这是 OpenCV 最基础的操作,是所有后续工作的起点。
3.1 读取图像
使用 cv2.imread()
函数读取图像。
“`python
import cv2
指定图像文件路径
image_path = ‘path/to/your/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(“图像读取成功!”)
# 图像是一个 NumPy 数组
print(“图像的形状 (高, 宽, 通道):”, img.shape)
print(“图像的数据类型:”, img.dtype)
“`
读取的图像是一个 NumPy 数组,形状通常是 (高度, 宽度, 通道数)
。对于彩色图像,通道数通常为 3 (BGR 顺序);对于灰度图像,通道数通常为 1。
3.2 显示图像
使用 cv2.imshow()
函数在窗口中显示图像。
“`python
import cv2
import numpy as np # 通常会一起使用 NumPy
创建一个简单的彩色图像 (例如,一个纯蓝色的图片)
注意 OpenCV 是 BGR 顺序
height, width = 200, 300
blue_image = np.zeros((height, width, 3), dtype=np.uint8)
blue_image[:, :] = (255, 0, 0) # 设置所有像素为蓝色 (B=255, G=0, R=0)
显示图像
cv2.imshow(‘Blue Image’, blue_image) # ‘Blue Image’ 是窗口标题
等待按键
cv2.waitKey(0): 无限期等待,直到任意键被按下
cv2.waitKey(milliseconds): 等待指定的毫秒数
返回按下的键的 ASCII 值,如果超时则返回 -1
print(“按下任意键关闭窗口…”)
key = cv2.waitKey(0)
关闭所有 OpenCV 窗口
cv2.destroyAllWindows()
print(“窗口已关闭。”)
“`
cv2.waitKey()
函数非常重要。在显示图像后,程序会继续执行,如果没有 waitKey
,窗口会瞬间闪现然后关闭。waitKey(0)
表示程序会暂停,直到用户按下键盘上的任意键才会继续。
3.3 保存图像
使用 cv2.imwrite()
函数将图像保存到文件。
“`python
import cv2
import numpy as np
创建一个示例图像
green_image = np.zeros((200, 300, 3), dtype=np.uint8)
green_image[:, :] = (0, 255, 0) # 绿色 (B=0, G=255, R=0)
指定保存路径和文件名
output_path = ‘green_image.png’
保存图像
文件扩展名决定了图像格式 (.png, .jpg, .bmp 等)
success = cv2.imwrite(output_path, green_image)
if success:
print(f”图像已成功保存到 ‘{output_path}'”)
else:
print(f”保存图像到 ‘{output_path}’ 失败”)
也可以保存读取的图像
original_image_path = ‘path/to/your/original_image.jpg’
img = cv2.imread(original_image_path)
if img is not None:
cv2.imwrite(‘saved_copy.jpg’, img)
print(“原始图像的副本已保存。”)
“`
imwrite()
的第一个参数是保存的文件路径(包含文件名和扩展名),第二个参数是要保存的图像 NumPy 数组。
4. 图像基本操作
图像在 OpenCV 中是 NumPy 数组,因此你可以使用 NumPy 的切片、索引等操作来处理图像。
4.1 访问像素
可以直接通过坐标访问图像中的像素值。记住,OpenCV 的坐标顺序是 (行, 列)
,对应于 NumPy 数组的 (y, x)
。彩色图像的通道顺序是 BGR。
“`python
import cv2
import numpy as np
img = cv2.imread(‘path/to/your/image.jpg’) # 替换为你的图像路径
if img is not None:
# 获取某个像素的 BGR 值 (例如,坐标 (100, 150) 处的像素)
# 注意:y=100, x=150
px = img[100, 150]
print(f”坐标 (150, 100) 处的像素值 (BGR): {px}”)
# 仅获取蓝色通道的值
blue = img[100, 150, 0]
print(f"坐标 (150, 100) 处的蓝色通道值: {blue}")
# 修改像素值 (将该像素设置为红色)
img[100, 150] = (0, 0, 255)
print(f"修改后像素值 (BGR): {img[100, 150]}")
# 对于灰度图像
# gray_img = cv2.imread('path/to/your/image.jpg', cv2.IMREAD_GRAYSCALE)
# if gray_img is not None:
# px_gray = gray_img[100, 150]
# print(f"灰度图像中坐标 (150, 100) 处的像素值: {px_gray}")
# gray_img[100, 150] = 200 # 修改灰度值
# print(f"修改后像素值: {gray_img[100, 150]}")
# 显示修改后的图像 (如果想看效果)
# cv2.imshow('Modified Image', img)
# cv2.waitKey(0)
# cv2.destroyAllWindows()
else:
print(“无法读取图像。”)
“`
4.2 访问图像属性
NumPy 数组提供了访问图像属性的方法:
“`python
import cv2
img = cv2.imread(‘path/to/your/image.jpg’) # 替换为你的图像路径
if img is not None:
print(“图像的形状 (高, 宽, 通道):”, img.shape)
print(“像素总数:”, img.size) # 高 * 宽 * 通道数
print(“图像的数据类型:”, img.dtype) # 通常是 uint8
对于灰度图像,shape 会是 (高, 宽)
gray_img = cv2.imread(‘path/to/your/image.jpg’, cv2.IMREAD_GRAYSCALE)
if gray_img is not None:
print(“灰度图像的形状 (高, 宽):”, gray_img.shape)
“`
4.3 图像的区域 (Region of Interest, ROI)
通过 NumPy 切片可以方便地选取图像的某个矩形区域。
“`python
import cv2
img = cv2.imread(‘path/to/your/image.jpg’) # 替换为你的图像路径
if img is not None:
# 假设你想截取图像左上角 100×150 的区域
# 记住 NumPy 切片是 [y1:y2, x1:x2]
roi = img[0:100, 0:150]
# 显示原始图像和 ROI
cv2.imshow('Original Image', img)
cv2.imshow('ROI', roi)
print("按下任意键关闭窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows()
# 你可以将 ROI 复制到图像的其他位置
# 确保 ROI 的大小和目标区域的大小一致
# img[200:300, 300:450] = roi # 例如,将 ROI 复制到右下角 (需要调整坐标和大小)
# cv2.imshow('Image with Copied ROI', img)
# cv2.waitKey(0)
# cv2.destroyAllWindows()
else:
print(“无法读取图像。”)
“`
4.4 通道分离与合并
彩色图像由多个通道组成(通常是 B, G, R)。有时需要单独处理某个通道,或者将处理后的通道重新合并。
“`python
import cv2
img = cv2.imread(‘path/to/your/image.jpg’) # 替换为你的图像路径
if img is not None:
# 分离通道
b, g, r = cv2.split(img)
# 现在 b, g, r 都是灰度图像 (单通道)
cv2.imshow('Blue Channel', b)
cv2.imshow('Green Channel', g)
cv2.imshow('Red Channel', r)
# 可以对单个通道进行操作,例如将红色通道全部置零
# img[:, :, 2] = 0 # 直接通过 NumPy 索引修改
# Or: r[:] = 0
# 合并通道
# 注意合并的顺序,这里是 BGR
img_merged = cv2.merge((b, g, r))
# 或者,如果修改了通道
# img_no_red = cv2.merge((b, g, r)) # 如果 r 被置零了
# 显示合并后的图像
cv2.imshow('Original (Merged)', img_merged)
# cv2.imshow('No Red Channel', img_no_red)
print("按下任意键关闭窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(“无法读取图像。”)
“`
注意: 直接使用 NumPy 索引 img[:,:,0]
, img[:,:,1]
, img[:,:,2]
来访问 B, G, R 通道比 cv2.split()
更高效,但 cv2.split()
在某些情况下可能更清晰。
4.5 图像算术运算
OpenCV 提供了图像的加法、减法、乘法、除法等运算。这些运算是在像素级别进行的。
“`python
import cv2
import numpy as np
img1 = cv2.imread(‘path/to/your/image1.png’) # 替换为你的图像路径
img2 = cv2.imread(‘path/to/your/image2.png’) # 替换为你的图像路径
确保两张图片大小和类型相同
if img1 is not None and img2 is not None and img1.shape == img2.shape and img1.dtype == img2.dtype:
# 图像加法
# cv2.add() 函数会进行饱和运算,即如果结果超过 255,则截断为 255
# NumPy 的加法会进行模运算,即 (250+10) % 256 = 4
img_add_cv = cv2.add(img1, img2)
img_add_np = img1 + img2 # NumPy 加法
cv2.imshow('Image 1', img1)
cv2.imshow('Image 2', img2)
cv2.imshow('cv2.add() Result', img_add_cv)
cv2.imshow('NumPy Add Result', img_add_np) # 可能会看到颜色异常
print("按下任意键关闭窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows()
# 图像混合 (加权加法)
#dst = alpha * img1 + beta * img2 + gamma
# alpha 和 beta 是权重,gamma 是一个常数偏移 (通常为 0)
# 要求 alpha + beta = 1 (但不是强制的,只是为了表示混合比例)
alpha = 0.7
beta = 0.3
img_blend = cv2.addWeighted(img1, alpha, img2, beta, 0)
cv2.imshow('Image Blend (alpha=0.7, beta=0.3)', img_blend)
print("按下任意键关闭窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(“无法读取图像或图像大小/类型不匹配。”)
“`
cv2.addWeighted()
函数常用于图像叠加,例如将水印叠加到图片上。
5. 在图像上绘制
OpenCV 提供了绘制点、线、矩形、圆、多边形和文本的函数。
“`python
import cv2
import numpy as np
创建一个纯黑色的画布
canvas = np.zeros((500, 500, 3), dtype=np.uint8)
1. 绘制直线
参数: img, 起始点坐标, 终止点坐标, 颜色 (BGR), 线条粗细
cv2.line(canvas, (0, 0), (500, 500), (255, 0, 0), 5) # 蓝色对角线
2. 绘制矩形
参数: img, 左上角坐标, 右下角坐标, 颜色, 线条粗细 (-1 表示填充)
cv2.rectangle(canvas, (100, 100), (400, 400), (0, 255, 0), 3) # 绿色矩形框
cv2.rectangle(canvas, (150, 150), (350, 350), (0, 0, 255), -1) # 红色填充矩形
3. 绘制圆
参数: img, 圆心坐标, 半径, 颜色, 线条粗细 (-1 表示填充)
cv2.circle(canvas, (250, 250), 80, (255, 255, 0), -1) # 黄色填充圆
4. 绘制多边形 (需要顶点坐标数组)
pts = np.array([[10, 5], [150, 10], [180, 100], [50, 150]], np.int32)
pts = pts.reshape((-1, 1, 2)) # 需要将顶点数组 reshape 成特定形状
参数: img, 顶点数组, 是否闭合, 颜色, 线条粗细
cv2.polylines(canvas, [pts], True, (255, 255, 255), 3) # 白色多边形
5. 添加文本
参数: img, 文本内容, 文本左下角坐标, 字体, 字体缩放比例, 颜色, 字体粗细, 线条类型
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(canvas, ‘OpenCV Tutorial’, (50, 480), font, 1, (255, 255, 255), 2, cv2.LINE_AA)
显示绘制结果
cv2.imshow(‘Drawing on Canvas’, canvas)
print(“按下任意键关闭窗口…”)
cv2.waitKey(0)
cv2.destroyAllWindows()
“`
这些绘制函数对于在图像上标注检测结果、创建掩膜等非常有用。
6. 处理视频和摄像头
OpenCV 可以轻松地读取、显示和保存视频文件,以及访问摄像头。
6.1 读取和播放视频
“`python
import cv2
创建一个 VideoCapture 对象
参数可以是视频文件路径 (例如 ‘my_video.mp4’) 或摄像头索引 (0, 1, …)
cap = cv2.VideoCapture(0) # 0 代表默认摄像头
检查摄像头是否成功打开
if not cap.isOpened():
print(“错误:无法打开摄像头或视频文件。”)
exit()
循环读取视频帧
while True:
# cap.read() 返回一个布尔值 (是否成功读取) 和一个视频帧 (NumPy 数组)
ret, frame = cap.read()
# 如果未能成功读取帧,则退出循环 (例如,视频结束或摄像头断开)
if not ret:
print("无法接收帧 (视频流结束?). 退出...")
break
# 在这里可以对每一帧进行图像处理
# 例如,将帧转换为灰度
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 显示原始帧和处理后的帧
cv2.imshow('Original Frame', frame)
cv2.imshow('Gray Frame', gray_frame)
# 按 'q' 键退出循环
# cv2.waitKey(1): 每毫秒等待 1 毫秒,处理界面事件。对于视频流,通常设置为 1 或更小
if cv2.waitKey(1) & 0xFF == ord('q'):
break
释放 VideoCapture 对象和关闭所有窗口
cap.release()
cv2.destroyAllWindows()
print(“视频播放/摄像头已关闭。”)
“`
6.2 保存视频
保存视频需要创建一个 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))
fps = int(cap.get(cv2.CAP_PROP_FPS)) # 尝试获取摄像头帧率,如果获取不到可能需要手动设置
定义视频编码器和创建 VideoWriter 对象
FourCC 是一个 4 字节的代码,用于指定视频编码器
例如:’XVID’, ‘MJPG’, ‘DIVX’, ‘X264’, ‘mp4v’ 等
‘XVID’ 通常是一个不错的跨平台选择
‘mp4v’ 或 ‘X264’ 可以生成 .mp4 文件 (可能需要额外的 codec)
fourcc = cv2.VideoWriter_fourcc(*’XVID’)
如果使用 ‘.mp4’ 扩展名,尝试 ‘mp4v’ 或 ‘X264’
fourcc = cv2.VideoWriter_fourcc(*’mp4v’)
output_filename = ‘output.avi’ # 或 ‘output.mp4’
out = cv2.VideoWriter(output_filename, fourcc, 20, (frame_width, frame_height)) # 设置帧率为 20,手动指定
print(f”正在录制视频到 ‘{output_filename}’…”)
while(cap.isOpened()):
ret, frame = cap.read()
if ret:
# 在这里可以对帧进行处理,例如添加文字或框
# cv2.putText(frame, ‘Recording…’, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
# 将处理后的帧写入文件
out.write(frame)
# 显示帧
cv2.imshow('Recording...', frame)
# 按 'q' 键停止录制
if cv2.waitKey(1) & 0xFF == ord('q'):
break
else:
break
释放资源
cap.release()
out.release()
cv2.destroyAllWindows()
print(“视频录制完成。”)
“`
选择正确的 FourCC 编码器和文件扩展名很重要,不同的操作系统和播放器支持的编码器不同。
7. 图像处理技术
OpenCV 提供了大量的图像处理函数,用于改变图像的外观、提取特征或进行预处理。
7.1 颜色空间转换
除了 BGR 和灰度,OpenCV 还支持 HSV、CIE L*a*b* 等颜色空间。HSV 颜色空间在基于颜色进行对象分割时非常有用,因为它将颜色信息(色调 H)与亮度(V)和饱和度(S)分离开来。
“`python
import cv2
img = cv2.imread(‘path/to/your/color_image.jpg’) # 替换为你的彩色图像路径
if img is not None:
# 转换为灰度图像
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 转换为 HSV 颜色空间
hsv_img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 显示不同颜色空间的图像
cv2.imshow('Original (BGR)', img)
cv2.imshow('Grayscale', gray_img)
cv2.imshow('HSV', hsv_img) # HSV 图像通常显示为彩色,但其通道含义不同
# 你可以分离 HSV 通道来查看 H, S, V 分量
# h, s, v = cv2.split(hsv_img)
# cv2.imshow('Hue', h)
# cv2.imshow('Saturation', s)
# cv2.imshow('Value', v)
print("按下任意键关闭窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(“无法读取图像。”)
“`
7.2 图像阈值化 (Thresholding)
阈值化是一种简单的分割技术,将图像像素根据阈值分为两类。
“`python
import cv2
img = cv2.imread(‘path/to/your/gray_image.jpg’, cv2.IMREAD_GRAYSCALE) # 读取灰度图像
if img is not None:
# 简单阈值化
# ret: 阈值 (如果你使用 THRESH_OTSU 或 THRESH_TRIANGLE)
# binary_img: 阈值处理后的二值图像
ret, binary_img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
ret, binary_inv_img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
ret, trunc_img = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)
ret, tozero_img = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)
ret, tozero_inv_img = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)
cv2.imshow('Original Grayscale', img)
cv2.imshow('THRESH_BINARY', binary_img)
cv2.imshow('THRESH_BINARY_INV', binary_inv_img)
cv2.imshow('THRESH_TRUNC', trunc_img)
cv2.imshow('THRESH_TOZERO', tozero_img)
cv2.imshow('THRESH_TOZERO_INV', tozero_inv_img)
# Otsu's Binarization (自动确定最佳阈值,适用于双峰直方图)
ret_otsu, otsu_img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
print(f"Otsu 自动确定的阈值: {ret_otsu}")
cv2.imshow('Otsu Binarization', otsu_img)
# 自适应阈值 (根据像素周围的小区域计算阈值,适用于光照不均匀的图像)
# cv2.ADAPTIVE_THRESH_MEAN_C: 阈值为邻域平均值减去常数 C
# cv2.ADAPTIVE_THRESH_GAUSSIAN_C: 阈值为邻域加权平均值减去常数 C (权重为高斯窗口)
# blockSize: 邻域大小 (奇数)
# C: 从平均值或加权平均值中减去的常数
adaptive_mean = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2)
adaptive_gaussian = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
cv2.imshow('Adaptive Mean Thresholding', adaptive_mean)
cv2.imshow('Adaptive Gaussian Thresholding', adaptive_gaussian)
print("按下任意键关闭窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(“无法读取图像。请确保是灰度图像路径。”)
“`
自适应阈值化对于扫描文档等场景非常有用,因为它能很好地处理光照变化。
7.3 几何变换
包括平移、旋转、缩放、仿射变换和透视变换。
“`python
import cv2
import numpy as np
img = cv2.imread(‘path/to/your/image.jpg’) # 替换为你的图像路径
if img is not None:
rows, cols, _ = img.shape
# 1. 平移 (Translation)
# M 是一个 2x3 的变换矩阵 [[1, 0, tx], [0, 1, ty]]
# tx 是 x 方向的平移量,ty 是 y 方向的平移量
tx, ty = 100, 50
M_trans = np.float32([[1, 0, tx], [0, 1, ty]])
# cv2.warpAffine() 应用仿射变换
translated_img = cv2.warpAffine(img, M_trans, (cols, rows)) # 输出图像大小通常与输入相同
cv2.imshow('Translated Image', translated_img)
print("显示平移后的图像...")
cv2.waitKey(0)
cv2.destroyAllWindows()
# 2. 旋转 (Rotation)
# 获取旋转矩阵
# 参数: 中心坐标, 旋转角度 (逆时针为正), 缩放比例
center = (cols / 2, rows / 2)
angle = 45
scale = 1.0
M_rot = cv2.getRotationMatrix2D(center, angle, scale)
rotated_img = cv2.warpAffine(img, M_rot, (cols, rows))
cv2.imshow('Rotated Image (45 deg)', rotated_img)
print("显示旋转后的图像...")
cv2.waitKey(0)
cv2.destroyAllWindows()
# 3. 缩放 (Scaling)
# 参数: 输入图像, 输出图像大小 (width, height), x 轴缩放因子, y 轴缩放因子, 插值方法
# fx, fy 为 0 时,输出大小由 dsize 决定;否则 dsize 可以是 None
# 插值方法: INTER_AREA (缩小), INTER_CUBIC (放大,较慢), INTER_LINEAR (放大,默认)
resized_img_down = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA)
resized_img_up = cv2.resize(img, None, fx=1.5, fy=1.5, interpolation=cv2.INTER_CUBIC)
cv2.imshow('Resized Down (0.5x)', resized_img_down)
cv2.imshow('Resized Up (1.5x)', resized_img_up)
print("显示缩放后的图像...")
cv2.waitKey(0)
cv2.destroyAllWindows()
# 4. 仿射变换 (Affine Transformation)
# 需要图像中三个非共线点的原坐标和变换后的坐标来计算 2x3 的变换矩阵
pts1 = np.float32([[50, 50], [200, 50], [50, 200]])
pts2 = np.float32([[10, 100], [200, 50], [100, 250]])
M_affine = cv2.getAffineTransform(pts1, pts2)
affine_img = cv2.warpAffine(img, M_affine, (cols, rows))
cv2.imshow('Affine Transformed Image', affine_img)
print("显示仿射变换后的图像...")
cv2.waitKey(0)
cv2.destroyAllWindows()
# 5. 透视变换 (Perspective Transformation)
# 需要图像中四个点的原坐标和变换后的坐标来计算 3x3 的变换矩阵
# 参数: 输入图像, 变换后的图像大小
pts_src = np.float32([[56, 65], [368, 52], [28, 387], [389, 390]]) # 原图像中的四个点 (例如,一个矩形的角)
pts_dst = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]]) # 变换后对应的四个点 (例如,一个标准矩形)
M_persp = cv2.getPerspectiveTransform(pts_src, pts_dst)
# cv2.warpPerspective() 应用透视变换
perspective_img = cv2.warpPerspective(img, M_persp, (300, 300)) # 输出图像大小可以自定义
cv2.imshow('Perspective Transformed Image', perspective_img)
print("显示透视变换后的图像...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(“无法读取图像。”)
“`
透视变换常用于消除图像的透视畸变,例如从某个角度拍摄的书本或平面物体。
7.4 形态学操作 (Morphological Operations)
形态学操作基于图像形状进行处理,通常用于二值图像。它们需要一个“结构元素”(或称为核)。
“`python
import cv2
import numpy as np
创建一个示例二值图像
例如,一些白色点和线条在黑色背景上
img = np.zeros((300, 300), dtype=np.uint8)
cv2.line(img, (50, 50), (250, 50), 255, 5)
cv2.circle(img, (150, 150), 30, 255, -1)
cv2.circle(img, (200, 200), 5, 255, -1) # 小点
cv2.imshow(‘Original Binary’, img)
print(“显示原始二值图像…”)
cv2.waitKey(0)
定义结构元素 (核)
kernel = np.ones((5, 5), np.uint8) # 5×5 的矩形核
1. 腐蚀 (Erosion)
作用:缩小白色区域,用于去除噪点
eroded_img = cv2.erode(img, kernel, iterations=1) # iterations 参数表示腐蚀的次数
cv2.imshow(‘Eroded Image’, eroded_img)
print(“显示腐蚀后的图像…”)
cv2.waitKey(0)
2. 膨胀 (Dilation)
作用:增大白色区域,用于连接断开的物体,或使细线变粗
dilated_img = cv2.dilate(img, kernel, iterations=1)
cv2.imshow(‘Dilated Image’, dilated_img)
print(“显示膨胀后的图像…”)
cv2.waitKey(0)
3. 开运算 (Opening)
作用:先腐蚀后膨胀,用于去除小噪点
opening_img = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
cv2.imshow(‘Opening Image’, opening_img)
print(“显示开运算后的图像…”)
cv2.waitKey(0)
4. 闭运算 (Closing)
作用:先膨胀后腐蚀,用于填充物体内部的小孔或连接临近的物体
closing_img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
cv2.imshow(‘Closing Image’, closing_img)
print(“显示闭运算后的图像…”)
cv2.waitKey(0)
5. 形态学梯度 (Morphological Gradient)
作用:膨胀图像减去腐蚀图像,得到物体的边缘
gradient_img = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
cv2.imshow(‘Morphological Gradient’, gradient_img)
print(“显示形态学梯度图像…”)
cv2.waitKey(0)
6. 顶帽 (Top Hat)
作用:原始图像减去开运算结果,得到图像中的亮细节 (例如,小亮点)
top_hat_img = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
cv2.imshow(‘Top Hat’, top_hat_img)
cv2.waitKey(0)
7. 黑帽 (Black Hat)
作用:闭运算结果减去原始图像,得到图像中的暗细节 (例如,小暗点)
black_hat_img = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
cv2.imshow(‘Black Hat’, black_hat_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
“`
形态学操作在处理二值图像的噪声、连接断开部分、分离粘连部分等方面非常有用。
7.5 图像平滑 (模糊)
图像平滑通过降低像素值与其邻域像素值的差异来减少图像噪声。
“`python
import cv2
import numpy as np
img = cv2.imread(‘path/to/your/noisy_image.jpg’) # 替换为你的含噪图像路径
if img is not None:
cv2.imshow(‘Original Noisy Image’, img)
print(“显示原始含噪图像…”)
cv2.waitKey(0)
# 1. 均值滤波 (Averaging)
# 作用:使用核区域内像素的平均值替换中心像素
# 参数: 输入图像, 核大小 (宽, 高)
mean_blur = cv2.blur(img, (5, 5))
cv2.imshow('Mean Blur (5x5)', mean_blur)
print("显示均值滤波后的图像...")
cv2.waitKey(0)
# 2. 高斯模糊 (Gaussian Blur)
# 作用:使用高斯核进行加权平均,中心像素权重最高
# 参数: 输入图像, 核大小 (宽, 高,必须是正奇数), X 方向标准差, Y 方向标准差 (通常与 X 相同或为 0)
gaussian_blur = cv2.GaussianBlur(img, (5, 5), 0)
cv2.imshow('Gaussian Blur (5x5)', gaussian_blur)
print("显示高斯模糊后的图像...")
cv2.waitKey(0)
# 3. 中值模糊 (Median Blur)
# 作用:使用核区域内像素的中值替换中心像素,对于椒盐噪声特别有效
# 参数: 输入图像, 核大小 (必须是大于 1 的奇数)
median_blur = cv2.medianBlur(img, 5)
cv2.imshow('Median Blur (5)', median_blur)
print("显示中值模糊后的图像...")
cv2.waitKey(0)
# 4. 双边滤波 (Bilateral Filter)
# 作用:在平滑的同时保留边缘,速度较慢
# 参数: 输入图像, 邻域直径, 颜色空间标准差, 坐标空间标准差
bilateral_filter = cv2.bilateralFilter(img, 9, 75, 75)
cv2.imshow('Bilateral Filter', bilateral_filter)
print("显示双边滤波后的图像...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(“无法读取图像。”)
“`
不同的模糊方法适用于不同类型的噪声和应用场景。中值滤波对椒盐噪声(孤立的黑白点)效果最好,双边滤波在降噪的同时能更好地保留边缘。
7.6 边缘检测 (Edge Detection)
边缘是图像中像素强度急剧变化的地方,它们是图像重要的特征。
“`python
import cv2
import numpy as np
img = cv2.imread(‘path/to/your/image.jpg’, cv2.IMREAD_GRAYSCALE) # 读取灰度图像
if img is not None:
cv2.imshow(‘Original Grayscale’, img)
print(“显示原始灰度图像…”)
cv2.waitKey(0)
# 1. Sobel 算子
# 作用:计算图像在 X 或 Y 方向的梯度近似
# 参数: 输入图像, 输出图像深度 (cv2.CV_64F 避免溢出), X 方向导数阶数, Y 方向导数阶数, Sobel 核大小
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5) # X 方向边缘
sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=5) # Y 方向边缘
# 计算梯度的幅度 (Magnitude)
# 将结果转回 uint8 类型进行显示 (可能需要缩放到 0-255)
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.convertScaleAbs(sobely)
sobel_combined = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0) # X 和 Y 方向边缘叠加
cv2.imshow('Sobel X', sobelx)
cv2.imshow('Sobel Y', sobely)
cv2.imshow('Sobel Combined', sobel_combined)
print("显示 Sobel 边缘检测结果...")
cv2.waitKey(0)
# 2. Scharr 算子
# 作用:Sobel 算子的一种优化形式,对中心点的权重更大,对小的梯度变化更敏感
# 参数与 Sobel 类似
scharrx = cv2.Scharr(img, cv2.CV_64F, 1, 0)
scharry = cv2.Scharr(img, cv2.CV_64F, 0, 1)
scharrx = cv2.convertScaleAbs(scharrx)
scharry = cv2.convertScaleAbs(scharry)
scharr_combined = cv2.addWeighted(scharrx, 0.5, scharry, 0.5, 0)
cv2.imshow('Scharr Combined', scharr_combined)
print("显示 Scharr 边缘检测结果...")
cv2.waitKey(0)
# 3. Laplacian 算子
# 作用:计算图像的二阶导数,对噪声敏感,常用于检测图像中的快速变化区域 (边缘)
# 参数: 输入图像, 输出图像深度
laplacian = cv2.Laplacian(img, cv2.CV_64F)
laplacian = cv2.convertScaleAbs(laplacian)
cv2.imshow('Laplacian', laplacian)
print("显示 Laplacian 边缘检测结果...")
cv2.waitKey(0)
# 4. Canny 边缘检测
# 作用:多级边缘检测算法,效果通常最好,抗噪能力强
# 步骤:高斯模糊 -> 计算梯度 -> 非最大值抑制 -> 双阈值边缘连接
# 参数: 输入图像, 低阈值, 高阈值
# 建议高阈值是低阈值的 2~3 倍
canny_edges = cv2.Canny(img, 100, 200) # 可以尝试不同的阈值组合
cv2.imshow('Canny Edges (100, 200)', canny_edges)
print("显示 Canny 边缘检测结果...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(“无法读取图像。请确保是灰度图像路径。”)
“`
Canny 边缘检测是实践中最常用的边缘检测算法之一。
7.7 轮廓 (Contours)
轮廓是连接具有相同颜色或强度值的连续曲线,它可以用来表示物体的边界。
“`python
import cv2
import numpy as np
通常在二值图像或灰度图像上查找轮廓
读取灰度图像并进行阈值化
img = cv2.imread(‘path/to/your/shapes.png’, cv2.IMREAD_GRAYSCALE) # 替换为包含一些形状的图像路径
if img is not None:
# 简单阈值化,获取二值图像
ret, thresh = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV) # 反转颜色,使物体成为白色
cv2.imshow('Thresholded Image', thresh)
print("显示阈值化图像...")
cv2.waitKey(0)
# 查找轮廓
# 参数: 输入图像 (二值图像), 轮廓检索模式, 轮廓近似方法
# cv2.RETR_EXTERNAL: 只检索最外层轮廓
# cv2.RETR_LIST: 检索所有轮廓,不建立层级关系
# cv2.RETR_CCOMP: 检索所有轮廓,并建立两级层级 (外层和内层孔洞)
# cv2.RETR_TREE: 检索所有轮廓,并建立完整的层级树
# cv2.CHAIN_APPROX_NONE: 存储所有轮廓点
# cv2.CHAIN_APPROX_SIMPLE: 压缩水平、垂直和对角线段,只保留端点
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 创建一个空白彩色图像用于绘制轮廓
img_with_contours = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8)
# 绘制所有轮廓
# 参数: 绘制图像, 轮廓列表, 轮廓索引 (-1 表示绘制所有轮廓), 颜色, 线条粗细
cv2.drawContours(img_with_contours, contours, -1, (0, 255, 0), 3) # 绿色绘制所有轮廓
cv2.imshow('Contours', img_with_contours)
print(f"找到 {len(contours)} 个轮廓。")
print("显示找到的轮廓...")
cv2.waitKey(0)
# 绘制单个轮廓 (例如,第一个轮廓)
if len(contours) > 0:
img_single_contour = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8)
cv2.drawContours(img_single_contour, contours, 0, (0, 0, 255), 3) # 红色绘制第一个轮廓
cv2.imshow('First Contour', img_single_contour)
print("显示第一个轮廓...")
cv2.waitKey(0)
# 轮廓属性示例
cnt = contours[0] # 获取第一个轮廓
area = cv2.contourArea(cnt) # 计算轮廓面积
perimeter = cv2.arcLength(cnt, True) # 计算轮廓周长 (True 表示闭合轮廓)
M = cv2.moments(cnt) # 计算轮廓矩
if M["m00"] != 0:
cx = int(M["m10"] / M["m00"]) # 计算轮廓中心 X 坐标
cy = int(M["m01"] / M["m00"]) # 计算轮廓中心 Y 坐标
print(f"第一个轮廓面积: {area}")
print(f"第一个轮廓周长: {perimeter}")
print(f"第一个轮廓中心: ({cx}, {cy})")
# 绘制外接矩形 (Bounding Box)
x, y, w, h = cv2.boundingRect(cnt)
cv2.rectangle(img_with_contours, (x, y), (x + w, y + h), (255, 0, 0), 2) # 蓝色绘制外接矩形
# 绘制最小外接圆 (Minimum Enclosing Circle)
(x, y), radius = cv2.minEnclosingCircle(cnt)
center = (int(x), int(y))
radius = int(radius)
cv2.circle(img_with_contours, center, radius, (0, 255, 255), 2) # 青色绘制最小外接圆
cv2.imshow('Contours with Properties', img_with_contours)
print("显示带属性的轮廓...")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(“无法读取图像。请确保是灰度图像路径。”)
“`
轮廓是图像分析和对象识别的重要步骤。你可以根据轮廓的面积、周长、形状特征来筛选和识别物体。
7.8 直方图 (Histograms)
直方图是图像像素强度分布的图形表示,它可以提供关于图像对比度、亮度等方面的信息。
“`python
import cv2
import numpy as np
import matplotlib.pyplot as plt # 使用 matplotlib 绘制直方图
img = cv2.imread(‘path/to/your/image.jpg’, cv2.IMREAD_GRAYSCALE) # 读取灰度图像
if img is not None:
# 计算灰度图像的直方图
# 参数: [图像数组], [使用的通道], 掩膜 (None 表示不使用), [直方图 bins 数量], [像素值范围]
hist = cv2.calcHist([img], [0], None, [256], [0, 256])
# 绘制直方图
plt.figure()
plt.title("Grayscale Histogram")
plt.xlabel("Pixel Value")
plt.ylabel("Frequency")
plt.plot(hist)
plt.xlim([0, 256])
plt.show() # 显示 Matplotlib 图形
# 对于彩色图像,可以分别计算各通道的直方图
color_img = cv2.imread('path/to/your/color_image.jpg')
if color_img is not None:
color = ('b','g','r')
plt.figure()
plt.title("Color Histogram")
plt.xlabel("Pixel Value")
plt.ylabel("Frequency")
for i, col in enumerate(color):
hist_color = cv2.calcHist([color_img], [i], None, [256], [0, 256])
plt.plot(hist_color, color = col)
plt.xlim([0, 256])
plt.show()
# 直方图均衡化 (Histogram Equalization)
# 作用:提高图像对比度,使像素值分布更均匀,只适用于灰度图像
equalized_img = cv2.equalizeHist(img)
cv2.imshow('Original Grayscale', img)
cv2.imshow('Equalized Grayscale', equalized_img)
print("显示直方图均衡化结果...")
cv2.waitKey(0)
cv2.destroyAllWindows()
# 也可以计算均衡化后的直方图并绘制
# hist_equalized = cv2.calcHist([equalized_img], [0], None, [256], [0, 256])
# plt.figure()
# plt.title("Equalized Grayscale Histogram")
# plt.xlabel("Pixel Value")
# plt.ylabel("Frequency")
# plt.plot(hist_equalized)
# plt.xlim([0, 256])
# plt.show()
else:
print(“无法读取图像。”)
“`
直方图均衡化是一种常用的增强图像对比度的方法。
7.9 模板匹配 (Template Matching)
模板匹配是在一个较大的图像中查找与给定模板图像相匹配的区域。
“`python
import cv2
import numpy as np
读取主图像 (包含目标) 和模板图像
img_rgb = cv2.imread(‘path/to/your/main_image.jpg’) # 替换为你的主图像路径
template = cv2.imread(‘path/to/your/template.jpg’, cv2.IMREAD_GRAYSCALE) # 替换为你的模板图像路径 (通常用灰度)
if img_rgb is not None and template is not None:
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY) # 将主图像也转为灰度进行匹配
# 获取模板图像的宽度和高度
w, h = template.shape[::-1] # 反转元组,获取 (宽度, 高度)
# 执行模板匹配
# 参数: 主图像, 模板图像, 比较方法
# cv2.TM_CCOEFF_NORMED: 相关系数匹配,归一化 (结果越大越匹配)
# cv2.TM_SQDIFF_NORMED: 平方差匹配,归一化 (结果越小越匹配)
# ... 其他方法
res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)
# 查找最佳匹配区域
# cv2.minMaxLoc() 返回最小值、最大值、最小值位置、最大值位置
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
# 根据使用的比较方法确定最佳匹配位置
# 对于 TM_CCOEFF_NORMED 等方法,最大值位置是最佳匹配的左上角
# 对于 TM_SQDIFF_NORMED 等方法,最小值位置是最佳匹配的左上角
threshold = 0.8 # 匹配阈值,根据实际情况调整
if cv2.TM_CCOEFF_NORMED > 0: # 例如,对于归一化相关系数
if max_val >= threshold:
top_left = max_loc # 最佳匹配区域的左上角坐标
bottom_right = (top_left[0] + w, top_left[1] + h) # 计算右下角坐标
# 在原始彩色图像上绘制矩形框
cv2.rectangle(img_rgb, top_left, bottom_right, (0, 255, 0), 2) # 绿色框
cv2.imshow('Template Matching Result', img_rgb)
print(f"找到匹配,最大匹配值: {max_val:.2f}")
cv2.waitKey(0)
else:
print(f"未找到匹配,最大匹配值: {max_val:.2f} (低于阈值 {threshold})")
# 也可以使用 np.where 来查找所有高于阈值的匹配位置
# loc = np.where( res >= threshold)
# for pt in zip(*loc[::-1]): # 遍历所有匹配位置 (pt 是左上角坐标)
# cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2) # 红色框
# cv2.imshow('Multiple Matches', img_rgb)
# cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print(“无法读取图像。”)
“`
模板匹配对于查找已知小物体(例如图标、特定图案)非常有用,但对旋转、缩放等变化敏感。
8. 高级话题简介
OpenCV 不仅仅包含上述基础操作,还有许多高级功能:
- 特征检测与匹配: SIFT, SURF (有时受专利限制), ORB (开源且高效), AKAZE, BRISK 等算法用于提取图像的关键点和描述子,常用于图像匹配、目标跟踪、三维重建等。
- 对象检测: Haar Cascades (用于人脸、眼睛等检测,速度快但准确性一般), HOG (用于行人检测), 以及与深度学习框架(如 TensorFlow, PyTorch)结合的 DNN 模块,支持加载 YOLO, SSD 等预训练模型进行对象检测。
- 目标跟踪: 各种跟踪算法,如 MedianFlow, KCF, CSRT 等。
- 图像分割: GrabCut (基于图割的交互式分割), Watershed (基于分水岭的分割) 等。
- 相机标定与三维重建: 计算相机内参、外参,消除畸变,基于双目或多目图像进行三维重建。
- 机器学习: 内建了一些传统的机器学习算法,如 SVM, K-Means, Decision Trees 等。
这些高级功能通常需要更深入的理解和更复杂的代码实现,超出了本文的范围,但它们是构建复杂计算机视觉系统的关键。
9. 进一步学习资源
- OpenCV 官方文档: https://docs.opencv.org/ 这是最权威的参考资料。Python 教程部分提供了很多基础和进阶的示例。
- OpenCV-Python Tutorials (官方): https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html
- 书籍: 《学习 OpenCV》、《OpenCV 4 计算机视觉项目实战》等。
- 在线课程和社区: Coursera, Udemy 等平台有相关的计算机视觉课程;Stack Overflow, OpenCV 官方论坛可以提问和交流。
10. 总结
本文详细介绍了 OpenCV Python 从环境搭建到图像处理的常用功能,包括图像的加载、显示、保存、基本操作(像素、ROI、通道)、绘制、视频处理以及阈值化、几何变换、形态学操作、图像平滑、边缘检测、轮廓、直方图、模板匹配等核心技术。
掌握这些基础知识是进一步学习和应用 OpenCV 的关键。计算机视觉是一个实践性很强的领域,最有效的学习方法是多动手、多实践。尝试修改代码、应用到自己的图片和视频上,解决具体的问题。
希望这篇教程能够帮助你打开计算机视觉的大门,祝你在 OpenCV Python 的学习旅程中取得成功!