使用 C/C++ 和 OpenCV 调用摄像头_c++调用摄像头
使用 C/C++ 和 OpenCV 调用摄像头 📸
OpenCV 是一个强大的计算机视觉库,它使得从摄像头捕获和处理视频流变得非常简单。本文将指导你如何使用 C/C++ 和 OpenCV 来调用摄像头、读取视频帧并进行显示。
准备工作
在开始之前,请确保你已经正确安装了 OpenCV 库,并且你的开发环境(如 Visual Studio, Code::Blocks, CLion, 或者使用 CMake/GCC 的命令行环境)已经配置好可以链接 OpenCV 库。
你需要包含以下头文件:
#include // 包含 OpenCV 的核心功能和高级 GUI (highgui)#include // 用于标准输入输出
1. 打开摄像头
要从摄像头捕获视频,我们首先需要创建一个 cv::VideoCapture
对象。它的构造函数可以接受一个整数作为参数,该整数表示摄像头的索引。通常,0
代表系统默认的内置摄像头,1
代表第一个外部摄像头,以此类推。
cv::VideoCapture cap; // 创建一个 VideoCapture 对象int cameraIndex = 0; // 通常 0 是默认摄像头cap.open(cameraIndex); // 或者直接 cv::VideoCapture cap(0);// 检查摄像头是否成功打开if (!cap.isOpened()) { std::cerr << \"错误: 无法打开摄像头 \" << cameraIndex << std::endl; return -1; // 或者进行其他错误处理}
提示:
- 你也可以传递一个视频文件的路径字符串给
cv::VideoCapture
的构造函数或open()
方法来读取视频文件。 - 如果有多个摄像头,你可以尝试不同的索引(0, 1, 2, …)直到找到你想要的摄像头。
2. 读取视频帧
一旦摄像头成功打开,我们就可以在一个循环中逐帧读取视频。cv::VideoCapture::read()
方法或重载的 >>
运算符可以用来获取新的帧。
read()
方法会返回一个布尔值,表示是否成功读取到一帧。读取到的帧会存储在一个 cv::Mat
对象中。
cv::Mat frame; // 创建一个 Mat 对象来存储每一帧while (true) { bool success = cap.read(frame); // 读取新的一帧 // 或者 cap >> frame; if (!success || frame.empty()) { std::cerr << \"错误: 无法从摄像头读取帧\" << std::endl; break; // 如果读取失败或帧为空,则退出循环 } // 在这里可以对 \'frame\' 进行处理,例如: // cv::cvtColor(frame, grayFrame, cv::COLOR_BGR2GRAY); // 转换为灰度图 // cv::GaussianBlur(frame, blurredFrame, cv::Size(5, 5), 0); // 高斯模糊 // ... (接下来的步骤:显示帧)}
3. 显示视频帧
OpenCV 的 highgui
模块提供了显示图像和视频的功能。我们可以使用 cv::imshow()
函数来显示捕获到的帧。通常还需要配合 cv::waitKey()
来控制帧的显示时间和处理用户输入。
// ... (在读取帧的循环内部) cv::imshow(\"摄像头画面\", frame); // 在名为 \"摄像头画面\" 的窗口中显示帧 // 等待按键,延迟 1 毫秒。 // 如果按下 \'ESC\'键 (ASCII 值为 27),则退出循环 // waitKey 返回按下键的 ASCII 值,如果没有按键则返回 -1 int key = cv::waitKey(1); if (key == 27) { // ESC 键 std::cout << \"ESC键被按下,正在关闭...\" << std::endl; break; } else if (key != -1) { // 可以添加其他按键的逻辑 // std::cout << \"按键: \" << key << std::endl; }
cv::waitKey(delay)
函数会等待指定的 delay
毫秒数。
- 如果
delay
为 0 或负数,它会无限期等待直到有按键按下。 - 如果
delay
为正数,它会等待delay
毫秒。如果在等待期间有按键按下,函数会返回按键的 ASCII 值;否则返回 -1。 - 对于视频流,通常使用一个较小的值(如 1 或 30)来确保视频流畅播放,并允许程序响应按键事件。
4. 释放资源
当不再需要摄像头或程序即将退出时,务必释放 cv::VideoCapture
对象,并销毁所有创建的窗口。
// ... (在主函数末尾或退出前)cap.release(); // 释放 VideoCapture 对象cv::destroyAllWindows(); //销毁所有由 OpenCV 创建的窗口
虽然 cv::VideoCapture
对象在析构时会自动释放摄像头,但显式调用 release()
是一个好习惯。
5. 获取和设置摄像头属性 (可选)
cv::VideoCapture
对象还允许你获取和设置摄像头的一些属性,例如帧的宽度、高度、FPS(每秒帧数)等。这些属性由 cv::CAP_PROP_*
枚举定义。
- 获取属性:
cap.get(cv::CAP_PROP_FRAME_WIDTH)
- 设置属性:
cap.set(cv::CAP_PROP_FRAME_WIDTH, newValue)
// 获取摄像头默认的帧宽度和高度double frameWidth = cap.get(cv::CAP_PROP_FRAME_WIDTH);double frameHeight = cap.get(cv::CAP_PROP_FRAME_HEIGHT);double fps = cap.get(cv::CAP_PROP_FPS);std::cout << \"默认宽度: \" << frameWidth << std::endl;std::cout << \"默认高度: \" << frameHeight << std::endl;std::cout << \"默认FPS: \" << fps << std::endl;// 尝试设置新的宽度和高度 (摄像头可能不支持所有值)// bool setWidthSuccess = cap.set(cv::CAP_PROP_FRAME_WIDTH, 1280);// bool setHeightSuccess = cap.set(cv::CAP_PROP_FRAME_HEIGHT, 720);// if (setWidthSuccess && setHeightSuccess) {// std::cout << \"成功设置分辨率为 1280x720\" << std::endl;// } else {// std::cout << \"警告: 未能成功设置期望的分辨率\" << std::endl;// // 再次获取实际生效的宽度和高度// frameWidth = cap.get(cv::CAP_PROP_FRAME_WIDTH);// frameHeight = cap.get(cv::CAP_PROP_FRAME_HEIGHT);// std::cout << \"当前宽度: \" << frameWidth << std::endl;// std::cout << \"当前高度: \" << frameHeight << std::endl;// }
注意: 并非所有摄像头都支持通过 set()
方法修改所有属性,或者可能只支持特定的预设值。设置后最好再次 get()
以确认实际生效的值。
完整示例代码
下面是一个将以上所有步骤整合在一起的完整示例:
#include #include int main() { // 1. 打开摄像头 cv::VideoCapture cap; int cameraIndex = 0; // 尝试不同的索引,如果默认摄像头不工作 cap.open(cameraIndex); // 检查摄像头是否成功打开 if (!cap.isOpened()) { std::cerr << \"错误: 无法打开摄像头 \" << cameraIndex << std::endl; // 尝试下一个摄像头索引,如果需要 // cameraIndex = 1; // cap.open(cameraIndex); // if (!cap.isOpened()) { // std::cerr << \"错误: 仍然无法打开摄像头 \" << cameraIndex << std::endl; // return -1; // } return -1; } std::cout << \"摄像头 \" << cameraIndex << \" 已成功打开.\" << std::endl; // (可选) 获取和打印摄像头属性 double frameWidth = cap.get(cv::CAP_PROP_FRAME_WIDTH); double frameHeight = cap.get(cv::CAP_PROP_FRAME_HEIGHT); double fps = cap.get(cv::CAP_PROP_FPS); std::cout << \"帧宽度: \" << frameWidth << std::endl; std::cout << \"帧高度: \" << frameHeight << std::endl; std::cout << \"FPS: \" << fps << std::endl; // 注意:FPS 可能不准确或不被所有摄像头支持 cv::Mat frame; // 用于存储每一帧 std::string windowName = \"摄像头画面 - 按 ESC 退出\"; cv::namedWindow(windowName, cv::WINDOW_AUTOSIZE); // 创建一个窗口 // 2. 读取并显示视频帧 while (true) { bool success = cap.read(frame); // 或者 cap >> frame; if (!success || frame.empty()) { std::cerr << \"错误: 无法从摄像头读取帧或视频已结束\" << std::endl; break; } // 在这里可以对 \'frame\' 进行图像处理 // 例如:显示帧号或时间戳 // cv::putText(frame, \"Frame: \" + std::to_string(cap.get(cv::CAP_PROP_POS_FRAMES)), // cv::Point(10, 30), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 255, 0), 2); // 3. 显示帧 cv::imshow(windowName, frame); // 等待按键,延迟 (1000/FPS) ms 以大致匹配视频帧率,或者简单用 1-30ms // 如果 fps > 0, 使用 int delay = 1000.0 / fps; 否则使用一个默认值 int delay = (fps > 0) ? static_cast<int>(1000.0 / fps) : 30; if (delay <= 0) delay = 30; // 防止 delay 为0或负数导致无限等待 int key = cv::waitKey(delay); // 等待约 30ms,或根据FPS计算 if (key == 27) { // ESC 键的 ASCII 值 std::cout << \"ESC键被按下,正在关闭...\" << std::endl; break; } else if (key == \'s\' || key == \'S\') { // 示例:按 \'s\' 保存当前帧 std::string filename = \"captured_frame.png\"; cv::imwrite(filename, frame); std::cout << \"当前帧已保存为 \" << filename << std::endl; } } // 4. 释放资源 cap.release(); cv::destroyAllWindows(); return 0;}
编译和运行 (以 g++ 为例):
假设你的 OpenCV 安装在标准路径,并且你已经设置了 pkg-config:
g++ your_code.cpp -o camera_app `pkg-config --cflags --libs opencv4`./camera_app
如果未使用 pkg-config,你可能需要手动指定包含目录和库文件:
g++ your_code.cpp -o camera_app -I/path/to/opencv/include -L/path/to/opencv/lib -lopencv_core -lopencv_highgui -lopencv_videoio -lopencv_imgproc./camera_app
(具体的库名称可能因 OpenCV 版本和模块而略有不同,如 opencv_videoio
是处理视频 I/O 的关键库)。
常见问题与调试技巧
- 无法打开摄像头:
- 确保摄像头已连接并且驱动程序已正确安装。
- 检查是否有其他应用程序正在使用该摄像头。
- 尝试不同的
cameraIndex
(0, 1, 2, …)。 - 在 Linux 上,检查
/dev/video*
设备文件是否存在以及你是否有权限访问它们。
- 视频流卡顿或延迟:
- 确保
cv::waitKey()
的延迟参数设置合理。太小的值可能导致 CPU 占用过高,太大的值则导致卡顿。 - 图像处理步骤如果过于复杂,会增加每帧的处理时间。
- 确保
- 窗口不显示或一闪而过:
- 确保在主循环之后调用
cv::destroyAllWindows()
,并且cv::waitKey()
在循环内部被正确调用以刷新窗口事件。
- 确保在主循环之后调用
- 帧为空 (
frame.empty()
为 true):- 这可能发生在视频文件结束,或者摄像头出现问题时。
希望这篇文章能帮助你成功使用 C/C++ 和 OpenCV 调用你的摄像头!