3、视频处理入门:读取摄像头视频、读取视频文件、保存视频、帧处理基础

好,咱们进入正题。视频处理,说白了就是跟一帧一帧的图片打交道。你想想看,视频不就是快速播放的图片序列吗?每秒24帧、30帧,甚至60帧,每一帧都是一张独立的图像。OpenCV处理视频的核心思路,就是把这个序列拆开,逐帧处理,再组合回去。

我个人习惯把视频处理分成三个步骤:读进来 → 处理每一帧 → 写出去。听起来简单,但坑不少。我刚开始做项目时,就因为在摄像头释放上栽过跟头——程序跑完,摄像头灯还亮着,得手动拔USB。嗯,咱们今天就把这些坑一个个填平。

3.1 读取摄像头视频

读取摄像头,OpenCV用的是 cv2.VideoCapture()。参数传0,就是调用默认摄像头。传1,就是第二个摄像头(比如外接USB摄像头)。

import cv2

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

# 检查是否成功打开
if not cap.isOpened():
    print("摄像头打开失败,请检查连接")
    exit()

while True:
    # 读取一帧
    ret, frame = cap.read()
    
    # ret是布尔值,True表示读取成功
    if not ret:
        print("无法获取帧,可能摄像头断开")
        break
    
    # 显示这一帧
    cv2.imshow('Camera', frame)
    
    # 按'q'键退出
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# 释放资源
cap.release()
cv2.destroyAllWindows()
⚠️ 我曾经犯过的错: 忘记调用 cap.release()。后果就是摄像头一直被占用,下次运行程序时提示“设备忙”。更严重的是,在某些笔记本上,摄像头灯会一直亮着,直到你重启电脑。所以,养成好习惯:用完就释放。

这里有个细节:cv2.waitKey(1) 的参数是1毫秒。为什么是1?因为要让OpenCV有时间处理窗口事件。如果你设成0,画面会卡住,因为程序在死等按键。我建议你保持1-10之间的值,既能流畅显示,又能及时响应按键。

3.2 读取视频文件

读取视频文件和读取摄像头几乎一样。区别就在于,把设备编号换成文件路径。

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

while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        print("视频播放完毕")
        break
    
    cv2.imshow('Video', frame)
    
    # 按空格暂停/继续
    if cv2.waitKey(25) & 0xFF == ord(' '):
        cv2.waitKey(0)  # 等待任意键继续
    
    if cv2.waitKey(25) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
💡 小技巧: 视频播放速度由 cv2.waitKey() 的参数控制。参数越大,每帧停留时间越长,播放越慢。对于30fps的视频,理论上 waitKey(33) 就是正常速度。但我个人习惯用25,因为实际读取和显示也有开销,25毫秒刚好接近30fps的节奏。

你可能会问:怎么知道视频的总帧数和帧率?OpenCV提供了几个属性接口:

# 获取视频属性
fps = cap.get(cv2.CAP_PROP_FPS)          # 帧率
width = cap.get(cv2.CAP_PROP_FRAME_WIDTH) # 宽度
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT) # 高度
total_frames = cap.get(cv2.CAP_PROP_FRAME_COUNT) # 总帧数

print(f"帧率: {fps}, 分辨率: {int(width)}x{int(height)}, 总帧数: {int(total_frames)}")
属性名称 OpenCV常量 说明
帧率 cv2.CAP_PROP_FPS 每秒显示的帧数
宽度 cv2.CAP_PROP_FRAME_WIDTH 视频帧的像素宽度
高度 cv2.CAP_PROP_FRAME_HEIGHT 视频帧的像素高度
总帧数 cv2.CAP_PROP_FRAME_COUNT 视频文件的总帧数(摄像头不适用)
当前帧位置 cv2.CAP_PROP_POS_FRAMES 当前读取到的帧序号

3.3 保存视频

保存视频比读取稍微复杂一点。你需要指定编码器、帧率、分辨率,然后逐帧写入。

# 定义编码器
fourcc = cv2.VideoWriter_fourcc(*'XVID')
# 或者用:cv2.VideoWriter_fourcc('X','V','I','D')

# 创建VideoWriter对象
out = cv2.VideoWriter('output.avi', fourcc, 20.0, (640, 480))

# 假设cap已经打开
while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        break
    
    # 处理帧(比如翻转)
    processed = cv2.flip(frame, 1)  # 水平翻转
    
    # 写入处理后的帧
    out.write(processed)
    
    cv2.imshow('Processing', processed)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

out.release()
cap.release()
cv2.destroyAllWindows()
🔑 关键点: VideoWriter 的分辨率必须和写入的帧分辨率一致。不一致的话,保存的视频会打不开,或者只有声音没有画面。我在项目中遇到过这个问题——折腾了半天,最后发现是resize忘了做。

常见的编码器有这些:

编码器标识 文件格式 说明
XVID .avi 兼容性好,文件较大
MJPG .avi Motion JPEG,质量高
H264 .mp4 压缩率高,文件小,但需要系统支持
DIVX .avi 类似XVID,老编码器
⚠️ 注意: H264编码在Windows上可能需要额外安装编码器。如果你发现保存的mp4文件打不开,试试换成XVID+avi组合。这是最稳妥的方案,我在三台不同电脑上测试过,都没问题。

3.4 帧处理基础

帧处理,就是你对每一张图片做的操作。咱们从最基础的开始:

3.4.1 颜色空间转换

# BGR转灰度
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

# BGR转HSV
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

# BGR转RGB(matplotlib显示用)
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

为什么是BGR不是RGB?OpenCV默认的颜色通道顺序是BGR,跟常见的RGB相反。我第一次用matplotlib显示OpenCV图像时,看到人脸是蓝色的,吓一跳。后来才反应过来——通道顺序搞反了。

3.4.2 图像缩放与裁剪

# 缩放
resized = cv2.resize(frame, (320, 240))

# 按比例缩放
scale_percent = 50  # 缩小到50%
width = int(frame.shape[1] * scale_percent / 100)
height = int(frame.shape[0] * scale_percent / 100)
dim = (width, height)
resized = cv2.resize(frame, dim, interpolation=cv2.INTER_AREA)

# 裁剪(数组切片)
cropped = frame[100:300, 200:400]  # 裁剪出矩形区域
💡 经验之谈: 缩小图像用 cv2.INTER_AREA,放大用 cv2.INTER_LINEARcv2.INTER_CUBIC。这是OpenCV官方推荐的,效果最好。我试过用默认的线性插值缩小图像,结果边缘出现锯齿,换成AREA后平滑多了。

3.4.3 在帧上绘制图形

# 画矩形(人脸检测常用)
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)

# 画圆
cv2.circle(frame, (center_x, center_y), radius, (0, 0, 255), -1)

# 写文字
cv2.putText(frame, 'Hello OpenCV', (50, 50), 
            cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)

参数说明:颜色是BGR格式的元组,比如(0, 255, 0)是绿色。最后一个参数是线条粗细,-1表示填充。

3.4.4 综合示例:实时边缘检测

import cv2

cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()
    if not ret:
        break
    
    # 1. 转灰度
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    # 2. 高斯模糊(去噪)
    blurred = cv2.GaussianBlur(gray, (5, 5), 0)
    
    # 3. Canny边缘检测
    edges = cv2.Canny(blurred, 50, 150)
    
    # 4. 显示结果
    cv2.imshow('Original', frame)
    cv2.imshow('Edges', edges)
    
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
🎯 核心思路: 帧处理就是“读一帧,处理一帧,显示/保存一帧”的循环。你可以在循环里做任何图像处理操作——滤波、检测、跟踪、绘制。这就是所有视频应用的基石。

嗯,到这里,视频处理的基础就讲完了。说白了就是三件事:读、处理、写。你想想看,后面我们要做的手势控制,本质上就是在每一帧里找到手的位置,然后根据位置或形状触发不同的操作。所以,把这一章的基础打牢,后面的内容就水到渠成了。

我个人建议你动手试一下:打开摄像头,在每一帧上画一个跟随鼠标移动的圆。这个小练习能帮你巩固帧处理循环的概念。我在教学生时,这个练习的完成率几乎是100%,因为它直观、有趣,而且能立刻看到效果。