3. 摄像头调用与图像采集
摄像头是人脸识别系统的「眼睛」。说实话,很多同学在算法上花了大把功夫,结果栽在摄像头调用上——画面卡顿、分辨率不对、甚至根本打不开。这一章,我们就来搞定 OpenCV 操作 USB 摄像头的全套流程。
本章核心目标:从物理连接摄像头,到实时显示视频流,再到按下快门采集高质量人脸图像,一气呵成。
3.1 初始化摄像头:cv2.VideoCapture 的正确打开方式
OpenCV 调用摄像头,核心就一个函数:cv2.VideoCapture()。参数传 0 表示默认摄像头,传 1 表示第二个摄像头。我见过有人插了 USB 摄像头还传 0,结果一直调用笔记本内置摄像头,画面模糊得不行。
import cv2
# 初始化摄像头,0 表示第一个摄像头设备
cap = cv2.VideoCapture(0)
# 检查是否成功打开
if not cap.isOpened():
print("错误:无法打开摄像头")
exit()
我曾经踩过的坑:在 Linux 系统上,如果摄像头被其他进程占用(比如浏览器正在使用),cap.isOpened() 返回 True 但读取不到帧。建议先检查设备文件 /dev/video0 是否存在,或者用 lsof /dev/video0 查看占用。
3.2 摄像头参数设置:分辨率与帧率
摄像头默认参数通常很「佛系」——640x480、30fps。但做人脸识别,我建议至少 1280x720,帧率 15fps 以上就够了。帧率太高反而增加 CPU 负担,你想想看,人脸识别又不是打电竞。
| 参数 | OpenCV 属性 ID | 推荐值 | 说明 |
|---|---|---|---|
| 帧宽度 | cv2.CAP_PROP_FRAME_WIDTH |
1280 | 高清人脸细节 |
| 帧高度 | cv2.CAP_PROP_FRAME_HEIGHT |
720 | 16:9 比例 |
| 帧率 | cv2.CAP_PROP_FPS |
15 | 平衡流畅与性能 |
| 亮度 | cv2.CAP_PROP_BRIGHTNESS |
自动/手动 | 环境光补偿 |
# 设置分辨率
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
# 设置帧率
cap.set(cv2.CAP_PROP_FPS, 15)
# 验证设置是否生效
actual_width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
actual_height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
actual_fps = cap.get(cv2.CAP_PROP_FPS)
print(f"实际分辨率: {actual_width} x {actual_height}")
print(f"实际帧率: {actual_fps}")
个人经验:有些廉价 USB 摄像头不支持你设置的分辨率,它会默默选择最接近的。所以一定要用 cap.get() 验证实际值。我在项目中遇到过标称 1080p 的摄像头,设置后实际只支持 800x600,人脸识别效果大打折扣。
3.3 实时视频流读取与显示
实时显示的核心是一个 while 循环,不断调用 cap.read() 读取帧,然后用 cv2.imshow() 显示。嗯,这里要注意:cap.read() 返回两个值——ret(布尔值,表示是否成功)和 frame(图像数据)。
while True:
# 读取一帧
ret, frame = cap.read()
if not ret:
print("无法读取帧,退出")
break
# 显示画面
cv2.imshow('Camera Feed', frame)
# 按 'q' 键退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 释放资源
cap.release()
cv2.destroyAllWindows()
为什么用 waitKey(1)?说白了,这个函数有两个作用:一是等待键盘输入,二是让 OpenCV 有时间处理窗口事件。参数 1 表示等待 1 毫秒,这样视频才能流畅播放。如果你写成 waitKey(0),画面会卡死。
3.4 拍照功能实现
拍照其实就是把当前帧保存成图片文件。我习惯用空格键触发拍照,因为空格键大、好按,操作起来顺手。
import os
from datetime import datetime
# 创建保存目录
save_dir = "captured_faces"
os.makedirs(save_dir, exist_ok=True)
photo_count = 0
while True:
ret, frame = cap.read()
if not ret:
break
# 在画面上显示提示文字
cv2.putText(frame, "Press SPACE to capture, 'q' to quit",
(10, 30), cv2.FONT_HERSHEY_SIMPLEX,
0.7, (0, 255, 0), 2)
cv2.imshow('Camera Feed', frame)
key = cv2.waitKey(1) & 0xFF
if key == ord(' '): # 空格键拍照
photo_count += 1
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{save_dir}/face_{timestamp}_{photo_count}.jpg"
cv2.imwrite(filename, frame)
print(f"已保存: {filename}")
# 拍照时闪一下白屏反馈
feedback = frame.copy()
cv2.putText(feedback, "Photo Captured!",
(frame.shape[1]//2 - 100, frame.shape[0]//2),
cv2.FONT_HERSHEY_SIMPLEX, 1.2, (255, 255, 255), 3)
cv2.imshow('Camera Feed', feedback)
cv2.waitKey(200) # 显示 200ms
elif key == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
关键设计点:
- 用时间戳命名文件,避免重名覆盖
- 拍照后给用户视觉反馈(闪白屏或文字提示)
- 创建独立保存目录,方便后续批量处理
3.5 避坑指南与最佳实践
做摄像头开发,坑是真的多。我整理了几个高频问题,你遇到了可以直接对照排查。
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 摄像头打不开 | 设备号错误 / 权限不足 | 尝试 cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)(Windows)或 sudo chmod 666 /dev/video0(Linux) |
| 画面卡顿 | 帧率设置过高 / 分辨率太大 | 降低 FPS 到 15,分辨率降到 640x480 测试 |
| 参数设置无效 | 摄像头不支持 / 设置顺序问题 | 先设置参数再读取第一帧;用 cap.get() 验证 |
| 拍照模糊 | 手抖 / 对焦慢 | 连续拍 3 帧取最清晰的;或设置自动对焦 |
我曾经遇到的诡异问题:在 Windows 上,如果直接 cv2.VideoCapture(0) 打不开摄像头,加上 cv2.CAP_DSHOW 后端参数就好了。这是因为 OpenCV 默认的 MSMF 后端在某些摄像头上有兼容性问题。代码改成:cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)。
3.6 完整代码整合
把上面的知识点串起来,就是一个可用的摄像头采集模块。我个人习惯把摄像头初始化和采集逻辑封装成类,方便后续集成到考勤系统里。
import cv2
import os
from datetime import datetime
class CameraCapture:
def __init__(self, camera_id=0, width=1280, height=720, fps=15):
self.cap = cv2.VideoCapture(camera_id, cv2.CAP_DSHOW)
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
self.cap.set(cv2.CAP_PROP_FPS, fps)
if not self.cap.isOpened():
raise RuntimeError("摄像头初始化失败")
self.save_dir = "captured_faces"
os.makedirs(self.save_dir, exist_ok=True)
self.photo_count = 0
def run(self):
while True:
ret, frame = self.cap.read()
if not ret:
break
cv2.imshow('Camera Feed', frame)
key = cv2.waitKey(1) & 0xFF
if key == ord(' '):
self._capture(frame)
elif key == ord('q'):
break
self.cap.release()
cv2.destroyAllWindows()
def _capture(self, frame):
self.photo_count += 1
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{self.save_dir}/face_{timestamp}_{self.photo_count}.jpg"
cv2.imwrite(filename, frame)
print(f"已保存: {filename}")
if __name__ == "__main__":
cam = CameraCapture()
cam.run()
最后说一句:摄像头采集是整个考勤系统的数据入口。如果这一步没做好,后面的人脸检测、特征提取全是白搭。建议你在实际部署前,先用这个模块在不同光照条件下测试一下,确保采集的图像质量稳定。