目录
10.1 OpenCV的Python接口
10.2 OpenCV基础知识
10.2.1 读取和写入图像
10.2.2 颜色空间
10.2.3 显示图像及结果
10.3 处理视频
10.3.1 视频输入
10.3.2 将视频读取到NumPy数组中
10.4 跟踪
10.4.1 光流
10.4.2 Lucas-Kanade算法
10.1 OpenCV的Python接口
OpenCV 是一个 C++ 库,它包含了计算机视觉领域的很多模块。除了 C++ 和 C, Python 作为一种简洁的脚本语言,在 C++ 代码基础上的 Python 接口得到了越来越广泛的支持。
OpenCV 2.3.1 版本实际上提供了两个 Python 接口。旧的 cv 模块使用 OpenCV 内部 数据类型,并且从 NumPy 使用起来可能需要一些技巧。新的 cv2 模块用到了 NumPy 数组,并且使用起来更加直观 1 ,可以通过以下方式导入新的 cv2 模块:
import cv2
10.2 OpenCV基础知识
10.2.1 读取和写入图像
载入一张图像,打印出图像大小,对图像进行转换并保存为 .png 格式:
实验代码:
python">import cv2# 读取图像
im = cv2.imread('suiyuan.jpg')# 获取图像尺寸
h, w = im.shape[:2]
print(h, w)# 保存图像为 .png 格式
cv2.imwrite('suiyuanresult.png', im)
分析:
- 读取图像:使用
cv2.imread
函数加载图像文件。 - 获取尺寸:通过
im.shape[:2]
获取图像的高度和宽度。 - 打印尺寸:输出图像的高度和宽度。
- 保存图像:使用
cv2.imwrite
函数将原图保存为 PNG 格式。
结果:
10.2.2 颜色空间
实验代码:
python">import cv2# 读取图像
im = cv2.imread('suiyuan.jpg')# 创建灰度图像
gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)# 获取原图像尺寸
h, w = im.shape[:2]
print(f'Original Image Size: {h}x{w}')# 保存灰度图像
cv2.imwrite('gray_image.png', gray)# 将原图像转换为 RGB 格式
rgb_image = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)
cv2.imwrite('result_rgb.png', rgb_image)
分析:
- 图像读取:使用
cv2.imread
函数加载图像文件 "suiyuan.jpg"。 - 颜色空间转换:
- 将原图像从 BGR 转换为灰度图像,使用
cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
。 - 还可以将 BGR 图像转换为 RGB 格式,使用
cv2.cvtColor(im, cv2.COLOR_BGR2RGB)
。
- 将原图像从 BGR 转换为灰度图像,使用
- 打印图像尺寸:输出原图像的高度和宽度。
- 保存图像:
- 保存灰度图像为 "gray_image.png"。
- 保存 RGB 图像为 "result_rgb.png"。
结果:
原图像:
灰度图像:
RGB图像
10.2.3 显示图像及结果
实验代码:
python">import cv2# 读取图像
im = cv2.imread('guimie.jpg')# 转换为灰度图像
gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)# 计算积分图像
intim = cv2.integral(gray)# 归一化积分图像并保存
intim_normalized = (255.0 * intim) / intim.max()
cv2.imwrite('result.jpg', intim_normalized)
分析:
- 图像读取:使用
cv2.imread
函数读取图像文件 "guimie.jpg"。 - 颜色空间转换:将图像转换为灰度图像,使用
cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
。 - 计算积分图像:通过
cv2.integral(gray)
创建积分图像,计算每个像素值为原图左上方所有像素强度的和,这在特征评估中非常有效。 - 归一化和保存:将积分图像归一化到 0-255 范围,并使用
cv2.imwrite
保存为 "result.jpg"。
结果:
10.3 处理视频
10.3.1 视频输入
实验代码:
python">import cv2# 打开视频文件
cap = cv2.VideoCapture('wanjia.mp4')# 获取视频的宽度和高度
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))# 定义视频编码和输出文件
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter('output_video.avi', fourcc, 20.0, (frame_width, frame_height))while True:ret, frame = cap.read()if not ret:break # 如果没有读取到帧,结束循环# 在帧上添加文本cv2.putText(frame, 'Video Output Experiment', (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)# 写入输出视频out.write(frame)# 显示当前帧cv2.imshow('Video', frame)# 按 'q' 键退出if cv2.waitKey(1) & 0xFF == ord('q'):break# 释放资源
cap.release()
out.release()
cv2.destroyAllWindows()
分析:
- 视频读取:使用
cv2.VideoCapture
打开视频文件。 - 获取视频信息:通过
get
方法获取视频的宽度和高度。 - 视频输出设置:使用
cv2.VideoWriter
定义输出视频文件,设置编码方式、帧率和帧大小。 - 处理每一帧:
- 在每帧上添加文本,使用
cv2.putText
。 - 将处理后的帧写入输出视频。
- 实时显示当前帧。
- 在每帧上添加文本,使用
- 退出条件:用户可以按 'q' 键退出视频播放。
- 资源释放:在结束时释放视频捕获和写入资源,并关闭所有窗口。
结果:
10.3.2 将视频读取到NumPy数组中
实验代码:
python">import cv2
import numpy as np# 打开视频文件
cap = cv2.VideoCapture('wanjia.mp4')# 创建一个空的列表来存储视频帧
frames = []# 读取视频帧
while True:ret, frame = cap.read()if not ret:break # 如果没有读取到帧,结束循环# 将帧添加到列表中frames.append(frame)# 关闭视频文件
cap.release()# 将列表转换为 NumPy 数组
video_array = np.array(frames)# 打印视频数组的形状
print("视频数组形状:", video_array.shape)# 可选:显示第一帧
cv2.imshow('First Frame', video_array[0])
cv2.waitKey(0)
cv2.destroyAllWindows()
分析:
- 打开视频文件:使用
cv2.VideoCapture
打开指定的视频文件。 - 读取视频帧:通过循环读取视频中的每一帧,使用
cap.read()
。- 如果读取成功,
ret
为True
,frame
为当前帧。 - 将每一帧添加到
frames
列表中。
- 如果读取成功,
- 释放资源:在读取完视频后,调用
cap.release()
释放视频文件。 - 转换为 NumPy 数组:使用
np.array(frames)
将帧列表转换为 NumPy 数组。 - 打印形状:输出视频数组的形状,可以看到数组的维度,形状为
(帧数, 高度, 宽度, 通道数)
。 - 显示第一帧:可选地展示第一帧,确保读取成功。
结果:
10.4 跟踪
10.4.1 光流
光流是目标、场景或摄像机在连续两帧图像间运动时造成的目标的运动。它是图像在平移过程中的二维矢量场。
光流法主要依赖于三个假设。
python">import cv2
import numpy as npdef draw_flow(im, flow, step=16):""" 在间隔分开的像素采样点处绘制光流 """h, w = im.shape[:2]y, x = np.mgrid[step/2:h:step, step/2:w:step].reshape(2, -1)fx, fy = flow[y.astype(int), x.astype(int)].T# 创建线的终点lines = np.vstack([x, y, x + fx, y + fy]).T.reshape(-1, 2, 2)lines = lines.astype(int)# 创建图像并绘制vis = cv2.cvtColor(im, cv2.COLOR_GRAY2BGR)for (x1, y1), (x2, y2) in lines:cv2.line(vis, (x1, y1), (x2, y2), (0, 255, 0), 1)cv2.circle(vis, (x1, y1), 1, (0, 255, 0), -1)return vis# 设置视频捕获
cap = cv2.VideoCapture(0)# 读取第一帧
ret, im = cap.read()
if not ret:print("无法读取视频流")exit()prev_gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)while True:# 获取灰度图像ret, im = cap.read()if not ret:breakgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)# 计算光流flow = cv2.calcOpticalFlowFarneback(prev_gray, gray, None, 0.5, 3, 15, 3, 5, 1.2, 0)prev_gray = gray# 画出光流cv2.imshow('Optical Flow', draw_flow(gray, flow))if cv2.waitKey(10) == 27: # 按 'ESC' 退出breakcap.release()
cv2.destroyAllWindows()
分析:
-
引入库:
cv2
用于计算机视觉处理。numpy
用于处理数组和矩阵。
-
函数
draw_flow
:- 输入图像和光流数据,在采样点处绘制光流矢量。
- 通过
np.mgrid
生成采样点,并利用光流计算终点。 - 绘制线条和圆点以可视化流动。
-
视频捕获:
- 使用
cv2.VideoCapture
捕获视频流。 - 读取第一帧并转换为灰度图。
- 使用
-
光流计算:
- 在循环中读取新帧,计算光流并更新前一帧的灰度图。
- 调用
draw_flow
函数可视化光流。
-
退出条件:
- 通过按
ESC
键退出循环,并释放视频捕获资源
- 通过按
结果:
10.4.2 Lucas-Kanade算法
跟踪最基本的形式是跟随感兴趣点,比如角点。对此,一次流行的算法是 Lucas-Kanade 跟踪算法,它利用了稀疏光流算法。
实验代码:
python">import cv2
import numpy as npdef lucas_kanade_optical_flow(video_path):cap = cv2.VideoCapture(video_path)ret, old_frame = cap.read()if not ret:print("无法读取视频")returnold_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, maxCorners=100, qualityLevel=0.3, minDistance=7, blockSize=7)mask = np.zeros_like(old_frame)while True:ret, frame = cap.read()if not ret:breakframe_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None)# 检查 p1 是否为 Noneif p1 is None or st is None:print("光流计算失败")breakgood_new = p1[st[:, 0] == 1] # 注意这里的索引good_old = p0[st[:, 0] == 1]for i, (new, old) in enumerate(zip(good_new, good_old)):a, b = map(int, new.ravel())c, d = map(int, old.ravel())mask = cv2.line(mask, (a, b), (c, d), (0, 255, 0), 2)frame = cv2.circle(frame, (a, b), 5, (0, 0, 255), -1)img = cv2.add(frame, mask)cv2.imshow('Optical Flow', img)old_gray = frame_gray.copy()p0 = good_new.reshape(-1, 1, 2)if cv2.waitKey(30) & 0xFF == 27:breakcap.release()cv2.destroyAllWindows()# 调用函数
video_path = 'wanjia.mp4' # 替换为你的视频路径
lucas_kanade_optical_flow(video_path)
分析:
-
光流原理:
- Lucas-Kanade 算法基于局部平滑性假设和亮度恒定性假设,利用图像的亮度变化来估计光流场。算法通过对邻域内的像素点进行线性最小二乘拟合,得到物体的运动信息。
-
参数设置:
maxCorners
:检测到的最大角点数量。qualityLevel
:角点质量的最小阈值。minDistance
:检测角点之间的最小距离。
结果:
-
运行代码后,将打开一个窗口,显示视频中物体的跟踪效果。
-
绿色线条表示跟踪的运动轨迹,红色圆点表示当前跟踪的角点。