跳到主要内容

视频处理

本章节介绍如何使用 OpenCV 处理视频,包括视频文件的读写、摄像头操作和视频分析技术。

视频读取

OpenCV 使用 cv2.VideoCapture 类来读取视频文件或摄像头数据。

读取视频文件

import cv2

# 打开视频文件
cap = cv2.VideoCapture('video.mp4')

# 检查是否成功打开
if not cap.isOpened():
print("无法打开视频文件")
exit()

while True:
# 读取一帧
ret, frame = cap.read()

# ret 为 True 表示读取成功,frame 为图像帧
if not ret:
print("视频播放结束")
break

# 显示帧
cv2.imshow('视频', frame)

# 按 q 退出
if cv2.waitKey(25) & 0xFF == ord('q'):
break

# 释放资源
cap.release()
cv2.destroyAllWindows()

read() 方法返回两个值:

  • ret:布尔值,表示是否成功读取帧
  • frame:读取的图像帧,是一个 NumPy 数组

读取摄像头

import cv2

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

if not cap.isOpened():
print("无法打开摄像头")
exit()

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

if not ret:
print("无法获取帧")
break

cv2.imshow('摄像头', frame)

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

cap.release()
cv2.destroyAllWindows()

如果有多个摄像头,可以尝试不同的索引值(0, 1, 2...)。

获取视频属性

import cv2

cap = cv2.VideoCapture('video.mp4')

# 获取视频属性
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS)
frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
duration = frame_count / fps # 视频时长(秒)

print(f"分辨率: {width}x{height}")
print(f"帧率: {fps} FPS")
print(f"总帧数: {frame_count}")
print(f"时长: {duration:.2f} 秒")

cap.release()

常用的视频属性:

属性说明
CAP_PROP_FRAME_WIDTH帧宽度
CAP_PROP_FRAME_HEIGHT帧高度
CAP_PROP_FPS帧率
CAP_PROP_FRAME_COUNT总帧数
CAP_PROP_POS_FRAMES当前帧位置
CAP_PROP_POS_MSEC当前位置(毫秒)

设置视频位置

import cv2

cap = cv2.VideoCapture('video.mp4')

# 跳转到第 100 帧
cap.set(cv2.CAP_PROP_POS_FRAMES, 100)

# 跳转到第 5 秒
cap.set(cv2.CAP_PROP_POS_MSEC, 5000)

ret, frame = cap.read()
if ret:
cv2.imshow('指定帧', frame)
cv2.waitKey(0)

cap.release()
cv2.destroyAllWindows()

视频写入

使用 cv2.VideoWriter 类将处理后的帧写入视频文件。

基本写入

import cv2

# 打开摄像头
cap = cv2.VideoCapture(0)

# 获取视频参数
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = 30.0

# 定义视频编码器和创建 VideoWriter
fourcc = cv2.VideoWriter_fourcc(*'mp4v') # MP4 编码
out = cv2.VideoWriter('output.mp4', fourcc, fps, (width, height))

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

# 写入帧
out.write(frame)

cv2.imshow('录制中', frame)

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

cap.release()
out.release()
cv2.destroyAllWindows()

视频编码器

常用的视频编码器:

编码器FourCC 代码文件格式
MP4V'mp4v'.mp4
XVID'XVID'.avi
MJPEG'MJPG'.avi
H264'avc1'.mp4
# 不同的编码器设置方式
fourcc = cv2.VideoWriter_fourcc(*'mp4v') # 方式1
fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') # 方式2

处理视频并保存

import cv2

cap = cv2.VideoCapture('input.mp4')

width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS)

fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter('output.mp4', fourcc, fps, (width, height))

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

# 转换为灰度图
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

# 转换回 BGR(视频写入需要 3 通道)
gray_bgr = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)

out.write(gray_bgr)

cv2.imshow('灰度视频', gray_bgr)

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

cap.release()
out.release()
cv2.destroyAllWindows()

视频分析

背景减除

背景减除用于从视频中提取前景对象:

import cv2

cap = cv2.VideoCapture('video.mp4')

# 创建背景减除器
# MOG2 背景减除器
back_sub = cv2.createBackgroundSubtractorMOG2(
history=500, # 历史帧数
varThreshold=16, # 方差阈值
detectShadows=True # 检测阴影
)

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

# 应用背景减除
fg_mask = back_sub.apply(frame)

# 显示结果
cv2.imshow('原图', frame)
cv2.imshow('前景掩码', fg_mask)

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

cap.release()
cv2.destroyAllWindows()

光流法

光流法用于检测视频中物体的运动:

import cv2
import numpy as np

cap = cv2.VideoCapture('video.mp4')

# 读取第一帧
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)

# 检测角点
feature_params = dict(
maxCorners=100,
qualityLevel=0.3,
minDistance=7,
blockSize=7
)
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)

# Lucas-Kanade 光流参数
lk_params = dict(
winSize=(15, 15),
maxLevel=2,
criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03)
)

# 创建掩码用于绘制轨迹
mask = np.zeros_like(old_frame)

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

frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

# 计算光流
p1, st, err = cv2.calcOpticalFlowPyrLK(
old_gray, frame_gray, p0, None, **lk_params
)

# 选择好的点
if p1 is not None:
good_new = p1[st == 1]
good_old = p0[st == 1]

# 绘制轨迹
for i, (new, old) in enumerate(zip(good_new, good_old)):
a, b = new.ravel()
c, d = old.ravel()
mask = cv2.line(mask, (int(a), int(b)), (int(c), int(d)),
(0, 255, 0), 2)
frame = cv2.circle(frame, (int(a), int(b)), 5, (0, 0, 255), -1)

img = cv2.add(frame, mask)
cv2.imshow('光流', img)

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

# 更新上一帧
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1, 1, 2)

cap.release()
cv2.destroyAllWindows()

帧差法

帧差法是最简单的运动检测方法:

import cv2

cap = cv2.VideoCapture('video.mp4')

ret, prev_frame = cap.read()
prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)
prev_gray = cv2.GaussianBlur(prev_gray, (21, 21), 0)

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

gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (21, 21), 0)

# 计算帧差
frame_diff = cv2.absdiff(prev_gray, gray)

# 二值化
_, thresh = cv2.threshold(frame_diff, 25, 255, cv2.THRESH_BINARY)

# 膨胀操作
thresh = cv2.dilate(thresh, None, iterations=2)

# 查找轮廓
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)

# 绘制运动区域
for contour in contours:
if cv2.contourArea(contour) < 500: # 过滤小区域
continue
x, y, w, h = cv2.boundingRect(contour)
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

cv2.imshow('运动检测', frame)
cv2.imshow('帧差', thresh)

prev_gray = gray.copy()

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

cap.release()
cv2.destroyAllWindows()

目标跟踪

OpenCV 提供了多种目标跟踪算法:

import cv2

cap = cv2.VideoCapture('video.mp4')

ret, frame = cap.read()
if not ret:
print("无法读取视频")
exit()

# 选择跟踪区域
bbox = cv2.selectROI('选择目标', frame, False)
cv2.destroyWindow('选择目标')

# 创建跟踪器(CSRT 算法)
tracker = cv2.TrackerCSRT_create()

# 初始化跟踪器
tracker.init(frame, bbox)

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

# 更新跟踪器
success, bbox = tracker.update(frame)

if success:
# 绘制跟踪框
x, y, w, h = [int(v) for v in bbox]
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.putText(frame, '跟踪中', (x, y - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)
else:
cv2.putText(frame, '丢失目标', (100, 100),
cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)

cv2.imshow('目标跟踪', frame)

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

cap.release()
cv2.destroyAllWindows()

常用的跟踪算法:

算法类名特点
CSRTTrackerCSRT_create精度高,速度较慢
KCFTrackerKCF_create速度快,精度中等
MILTrackerMIL_create鲁棒性好
MOSSETrackerMOSSE_create速度最快

实用示例

视频录制工具

import cv2
from datetime import datetime

def record_video(output_path=None, duration=10):
cap = cv2.VideoCapture(0)

width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = 30.0

if output_path is None:
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
output_path = f'record_{timestamp}.mp4'

fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))

frame_count = 0
total_frames = int(duration * fps)

print(f"开始录制,时长: {duration} 秒")

while frame_count < total_frames:
ret, frame = cap.read()
if not ret:
break

out.write(frame)
cv2.imshow('录制中', frame)

frame_count += 1

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

print(f"录制完成,保存到: {output_path}")

cap.release()
out.release()
cv2.destroyAllWindows()

record_video(duration=5)

运动检测报警

import cv2
import numpy as np

def motion_detection(threshold=500):
cap = cv2.VideoCapture(0)

ret, prev_frame = cap.read()
prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)
prev_gray = cv2.GaussianBlur(prev_gray, (21, 21), 0)

motion_detected = False

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

gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (21, 21), 0)

frame_diff = cv2.absdiff(prev_gray, gray)
_, thresh = cv2.threshold(frame_diff, 25, 255, cv2.THRESH_BINARY)
thresh = cv2.dilate(thresh, None, iterations=2)

contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)

motion = False
for contour in contours:
if cv2.contourArea(contour) < threshold:
continue
motion = True
x, y, w, h = cv2.boundingRect(contour)
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

if motion and not motion_detected:
print("检测到运动!")
motion_detected = True
elif not motion:
motion_detected = False

status = "运动检测" if motion else "正常"
cv2.putText(frame, status, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255) if motion else (0, 255, 0), 2)

cv2.imshow('运动检测', frame)

prev_gray = gray.copy()

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

cap.release()
cv2.destroyAllWindows()

motion_detection()

下一步

掌握了视频处理后,下一章节我们将学习特征检测与匹配,了解如何检测图像中的关键点并进行特征匹配。