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()

最后说一句:摄像头采集是整个考勤系统的数据入口。如果这一步没做好,后面的人脸检测、特征提取全是白搭。建议你在实际部署前,先用这个模块在不同光照条件下测试一下,确保采集的图像质量稳定。