使用 C/C++ 和 OpenCV 调用摄像头
使用 C/C++ 和 OpenCV 调用摄像头 📸
OpenCV 是一个强大的计算机视觉库,它使得从摄像头捕获和处理视频流变得非常简单。本文将指导你如何使用 C/C++ 和 OpenCV 来调用摄像头、读取视频帧并进行显示。
准备工作
在开始之前,请确保你已经正确安装了 OpenCV 库,并且你的开发环境(如 Visual Studio, Code::Blocks, CLion, 或者使用 CMake/GCC 的命令行环境)已经配置好可以链接 OpenCV 库。
你需要包含以下头文件:
#include <opencv2/opencv.hpp> // 包含 OpenCV 的核心功能和高级 GUI (highgui)
#include <iostream> // 用于标准输入输出
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 值,如果没有按键则返回 -1int 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 <opencv2/opencv.hpp>
#include <iostream>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 调用你的摄像头!
相关文章:
使用 C/C++ 和 OpenCV 调用摄像头
使用 C/C 和 OpenCV 调用摄像头 📸 OpenCV 是一个强大的计算机视觉库,它使得从摄像头捕获和处理视频流变得非常简单。本文将指导你如何使用 C/C 和 OpenCV 来调用摄像头、读取视频帧并进行显示。 准备工作 在开始之前,请确保你已经正确安装…...
历史数据分析——广州港
个股简介 公司简介: 华南地区最大的综合性主枢纽港。 本公司是由广州港集团、国投交通、广州发展作为发起人,共同出资以发起方式设立的股份有限公司。 经营分析: 一般经营项目:企业管理服务(涉及许可经营项目的除外);港务船舶调度服务;船舶通信服务;企业自有资金…...

数据库管理与高可用-MySQL全量,增量备份与恢复
目录 #1.1MySQL数据库备份概述 1.1.1数据备份的重要性 1.1.2数据库备份类型 1.1.3常见的备份方法 #2.1数据库完全备份操作 2.1.1物理冷备份与恢复 2.1.2mysqldump备份与恢复 2.1.3MySQL增量备份与恢复 #3.1制定企业备份策略的思路 #4.1扩展:MySQL的GTID 4.1.1My…...

从gitee仓库中恢复IDEA项目某一版本
神奇的功能!!!代码改乱了,但是还有救! 打开终端,输入git log 复制想要恢复版本的提交哈希值,打开终端输入git reset --hard <哈希值> ,就能修复到那时的提交版本了...

用dayjs解析时间戳,我被提了bug
引言 前几天开发中突然接到测试提的一个 Bug,说我的时间组件显示异常。 我很诧异,这里初始化数据是后端返回的,我什么也没改,这bug提给我干啥。我去问后端:“这数据是不是有问题?”。后端答:“…...
[git每日一句]Changes not staged for commit
在 Git 中,"Changes not staged for commit" 的意思是: 你有已修改的文件,但尚未使用 git add 将它们添加到暂存区(Staging Area),因此这些更改不会被包含在下次提交中。 具体含义 已修改但未暂…...
架构师面试题整理
以下是从提供的HTML代码中提取的所有class"title-txt"的文本内容,已排除重复项并按顺序整理: 缓存专题 实战解决大规模缓存击穿导致线上数据库压力暴增面试常问的缓存穿透是怎么回事基于DCL机制解决突发性热点缓存并发重建问题实战Redis分布…...

类和对象:实现日期类
目录 概述 一.实现日期类的基本框架 二.实现比较的运算符重载 1.>的运算符重载 2.的运算符重载 3.其余的比较运算符重载 三.加减天数的运算符重载 1.,的运算符重载 2.-,-的运算符重载 3.对1和2的小优化 四.两个日期类相减的重载 1.,--的重…...

基于springboot的运动员健康管理系统
博主介绍:java高级开发,从事互联网行业六年,熟悉各种主流语言,精通java、python、php、爬虫、web开发,已经做了六年的毕业设计程序开发,开发过上千套毕业设计程序,没有什么华丽的语言࿰…...

华为云Flexus+DeepSeek征文 | 初探华为云ModelArts Studio:部署DeepSeek-V3/R1商用服务的详细步骤
华为云FlexusDeepSeek征文 | 初探华为云ModelArts Studio:部署DeepSeek-V3/R1商用服务的详细步骤 前言一、华为云ModelArts Studio平台介绍1.1 ModelArts Studio介绍1.2 ModelArts Studio主要特点1.3 ModelArts Studio使用场景1.4 ModelArts Studio产品架构 二、访问…...

下载即转化的商业密码:解析华为应用商店CPD广告的智能投放逻辑
在移动互联网流量红利见顶的背景下,华为应用市场凭借其终端生态优势正成为开发者获客的新蓝海。数据显示,2025年Q1华为应用商店全球分发量同比增长27%,其中CPD广告因其"下载才付费"的精准特性,已成为金融、游戏、工具类…...

分布式锁和数据库锁完成接口幂等性
1、分布式锁 唯一主键与乐观锁的本质是使用了数据库的锁,但由于数据库锁的性能不太好,所以我们可使用Redis、Zookeeper等中间件来实现分布式锁的功能,以Redis为例实现幂等:当用户通过浏览器发起请求,服务端接收到请求…...

浅谈JMeter之常见问题Address already in use: connect
浅谈JMeter之常见问题Address already in use: connect 在JMeter高并发测试中出现“address already in use”错误,主要源于Windows系统的TCP端口资源耗尽及连接配置问题,在执行JMeter中查看结果树 原因分析 GET请求默认采用短连接(Conne…...

【机器学习基础】机器学习入门核心算法:随机森林(Random Forest)
机器学习入门核心算法:随机森林(Random Forest) 1. 算法逻辑2. 算法原理与数学推导2.1 核心组件2.2 数学推导2.3 OOB(Out-of-Bag)误差 3. 模型评估评估指标特征重要性可视化 4. 应用案例4.1 医疗诊断4.2 金融风控4.3 遥…...

【深度学习】12. VIT与GPT 模型与语言生成:从 GPT-1 到 GPT4
VIT与GPT 模型与语言生成:从 GPT-1 到 GPT4 本教程将介绍 GPT 系列模型的发展历程、结构原理、训练方式以及人类反馈强化学习(RLHF)对生成对齐的改进。内容涵盖 GPT-1、GPT-2、GPT-3、GPT-3.5(InstructGPT)、ChatGPT …...

常规算法学习
算法 1. 排序算法1. 归并排序1.1 普通归并排序1.2 优化后的归并排序(TimSort) 2. 插入排序2.1 直接插入排序2.2 二分插入排序2.3 成对插入排序 3. 快速排序3.1 单轴快速排序3.2 双轴快排 4. 计数排序 2. 树1. 红黑树(Red Black Treeÿ…...

Google 发布的全新导航库:Jetpack Navigation 3
前言 多年来,Jetpack Navigation 库一直是开发者的重要工具,但随着 Android 用户界面领域的发展,特别是大屏设备的出现和 Jetpack Compose 的兴起,Navigation 的功能也需要与时俱进。 今年的 Google I/O 上重点介绍了 Jetpack Na…...

Arbitrum Stylus 合约实战 :Rust 实现 ERC20
在《Arbitrum Stylus 深入解析与 Rust 合约部署实战》篇中,我们深入探讨了 Arbitrum Stylus 的核心技术架构,包括其 MultiVM 机制、Rust 合约开发环境搭建,以及通过 cargo stylus 实现简单计数器合约的部署与测试。Stylus 作为 Arbitrum Nitr…...
电脑故障基础知识
1.1 了解电脑故障 分类:分为软件故障(系统感染病毒、程序错误)和硬件故障(硬件物理损坏、接触不良)。 原因:人为操作失误、病毒破坏、工作环境恶劣(高温 / 灰尘)、硬件老化。 准备工…...
12.2Swing中JButton简单分析
JButton 的继承结构 public class JButton extends AbstractButton implements Accessible AbstractButton 是所有 Swing 按钮类(如 JToggleButton, JRadioButton, JCheckBox)的基类。它封装了按钮的核心逻辑:图标、文本、边框、动作事件等…...

内存管理--《Hello C++ Wrold!》(8)--(C/C++)--深入剖析new和delete的使用和底层实现
文章目录 前言C/C内存分布new和deletenew和delete的底层定位new表达式 内存泄漏作业部分 前言 在C/C编程中,内存管理是理解程序运行机制的核心基础,也是开发高效、稳定程序的关键。无论是局部变量的存储、动态内存的分配,还是对象生命周期的…...
JavaScript性能优化实战指南(详尽分解版)
JavaScript性能优化实战指南 一、加载优化 减少HTTP请求 // 合并CSS/JS文件 // 使用雪碧图CSS Sprites .icon {background-image: url(sprites.png);background-position: -20px 0; }代码分割与懒加载 // 动态导入模块 button.addEventListener(click, async () > {cons…...
从 AMQP 到 RabbitMQ:核心组件设计与工作原理(一)
一、引言 ** 在当今分布式系统盛行的时代,消息队列作为一种关键的中间件技术,承担着系统间异步通信、解耦和削峰填谷的重要职责。AMQP(Advanced Message Queuing Protocol)作为一种高级消息队列协议,为消息队列的实现…...

Java进阶---JVM
JVM概述 JVM作用: 负责将字节码翻译为机器码,管理运行时内存 JVM整体组成部分: 类加载系统(ClasLoader):负责将硬盘上的字节码文件加载到内存中 运行时数据区(RuntimeData Area):负责存储运行时各种数据 执行引擎(Ex…...
鸿蒙OSUniApp离线优先数据同步实战:打造无缝衔接的鸿蒙应用体验#三方框架 #Uniapp
UniApp离线优先数据同步实战:打造无缝衔接的鸿蒙应用体验 最近在开发一个面向鸿蒙生态的UniApp应用时,遇到了一个有趣的挑战:如何在网络不稳定的情况下保证数据的实时性和可用性。经过一番探索和实践,我们最终实现了一套行之有效…...
地震资料裂缝定量识别——学习计划
学习计划 地震资料裂缝定量识别——理解常规采集地震裂缝识别方法纵波各向异性方法蚁群算法相干体及倾角检测方法叠后地震融合属性方法裂缝边缘检测方法 非常规采集地震裂缝识别方法P-S 转换波方法垂直地震剖面方法 学习计划 地震资料裂缝定量识别——理解 地震资料裂缝识别&a…...

C++ 检查一条线是否与圆接触或相交(Check if a line touches or intersects a circle)
给定一个圆的圆心坐标、半径 > 1 的圆心坐标以及一条直线的方程。任务是检查给定的直线是否与圆相交。有三种可能性: 1、线与圆相交。 2、线与圆相切。 3、线在圆外。 注意:直线的一般方程是 a*x b*y c 0,因此输入中只给出常数 a、b、…...
23. Merge k Sorted Lists
目录 题目描述 方法一、k-1次两两合并 方法二、分治法合并 方法三、使用优先队列 题目描述 23. Merge k Sorted Lists 方法一、k-1次两两合并 选第一个链表作为结果链表,每次将后面未合并的链表合并到结果链表中,经过k-1次合并,即可得到…...
每日算法刷题计划Day20 6.2:leetcode二分答案3道题,用时1h20min
9.3048.标记所有下标的最早秒数(中等) 3048. 标记所有下标的最早秒数 I - 力扣(LeetCode) 思想 1.给你两个下标从 1 开始的整数数组 nums 和 changeIndices ,数组的长度分别为 n 和 m 。 一开始,nums 中所有下标都是未标记的&a…...
Spring Security安全实践指南
安全性的核心价值 用户视角的数据敏感性认知 从终端用户角度出发,每个应用程序都涉及不同级别的数据敏感度。以电子邮件服务与网上银行为例:前者内容泄露可能仅造成隐私困扰,而后者账户若被操控将直接导致财产损失。这种差异体现了安全防护需要分级实施的基本原则: // 伪…...