引言
在这个博文中,我们将详细介绍如何使用Qt框架结合OpenCV库来实现一个摄像头操作的Demo。这个Demo将涵盖打开摄像头、显示视频流以及拍照和录像等功能。
一、准备工作
首先,确保你的开发环境中已经安装了Qt和OpenCV。Qt是一个跨平台的C++图形用户界面应用程序开发框架,而OpenCV是一个开源的计算机视觉和机器学习软件库。
安装Qt
- 访问Qt官方网站下载并安装Qt。
- 安装时确保包含了Qt Widgets模块,因为我们将使用QWidget来显示视频流。
安装OpenCV
- 你可以从OpenCV的官方网站下载源代码并编译,或者使用预编译的库。
- 确保将OpenCV的库文件添加到你的Qt项目的链接器路径中,并将头文件目录添加到包含路径中。
二、创建Qt项目
- 打开Qt Creator,选择“File” -> “New File or Project”。
- 选择“Qt Widgets Application”,然后点击“Choose…”。
- 为项目提供一个名称,选择合适的路径,然后点击“Next”和“Finish”完成项目的创建。
三、配置项目
在Qt项目的.pro
文件中,你需要添加OpenCV的库依赖。例如:
INCLUDEPATH += /path/to/opencv/include
LIBS += -L/path/to/opencv/lib \
-lopencv_core \
-lopencv_imgproc \
-lopencv_highgui \
-lopencv_videoio \
-lopencv_imgcodecs
请根据实际情况替换/path/to/opencv
为你自己的OpenCV安装路径。
四、实现摄像头操作
1. 包含必要的头文件
在你的主窗口类文件中,包含必要的Qt和OpenCV头文件:
#include <QMainWindow>
#include <QLabel>
#include <QTimer>
#include <opencv2/opencv.hpp>
2. 添加成员变量
在你的主窗口类中添加VideoCapture对象、QLabel对象以及QTimer对象作为成员变量:
private:
cv::VideoCapture capture;
QLabel *videoLabel;
QTimer *timer;
3. 初始化摄像头
在你的主窗口构造函数中或某个初始化函数中,打开摄像头并设置定时器:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
videoLabel = new QLabel(this);
videoLabel->setGeometry(QRect(10, 10, 640, 480));
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &MainWindow::updateFrame);
capture.open(0); // 打开默认摄像头
if (!capture.isOpened()) {
qDebug() << "Failed to open camera!";
return;
}
capture.set(cv::CAP_PROP_FRAME_WIDTH, 640);
capture.set(cv::CAP_PROP_FRAME_HEIGHT, 480);
capture.set(cv::CAP_PROP_FPS, 30);
timer->start(33); // 设置定时器为33ms,大约30fps
}
4. 更新视频帧
实现updateFrame
槽函数,该函数将摄像头捕获的帧转换为QImage并显示在QLabel上:
void MainWindow::updateFrame()
{
cv::Mat frame;
if (capture.read(frame)) {
cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB);
QImage img((uchar*)frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888);
videoLabel->setPixmap(QPixmap::fromImage(img));
}
}
5. 调整摄像头参数
你可以添加函数来调整摄像头的亮度、对比度等参数。例如:
void MainWindow::setBrightness(int brightness)
{
capture.set(cv::CAP_PROP_BRIGHTNESS, brightness);
}
void MainWindow::setContrast(int contrast)
{
capture.set(cv::CAP_PROP_CONTRAST, contrast);
}
6. 拍照
实现拍照功能,你可以在Qt界面中添加一个按钮,当用户点击这个按钮时,捕获当前摄像头的帧并保存到文件中。这里我们可以使用OpenCV的imwrite
函数来保存图片。
首先,在你的Qt界面中添加一个QPushButton,并为其设置槽函数,比如命名为on_captureButton_clicked()
。
// 假设你在MainWindow的构造函数或某个初始化函数中已经添加了QPushButton并设置了槽函数
QPushButton *captureButton = new QPushButton("Capture", this);
captureButton->setGeometry(QRect(10, 500, 100, 30));
connect(captureButton, &QPushButton::clicked, this, &MainWindow::on_captureButton_clicked);
然后,实现on_captureButton_clicked()
槽函数:
void MainWindow::on_captureButton_clicked()
{
cv::Mat frame;
if (capture.read(frame)) {
// 转换颜色空间(如果需要)
cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB);
// 设置图片保存路径
QString fileName = QFileDialog::getSaveFileName(this, "Save Image", "", "Image Files (*.png *.jpg *.bmp)");
if (!fileName.isEmpty()) {
std::vector<int> compression_params;
compression_params.push_back(cv::IMWRITE_PNG_COMPRESSION);
compression_params.push_back(9); // 压缩等级0-9,9为最高质量
// 将QString转换为std::string
std::string filePath = fileName.toStdString();
// 保存图片
bool isSaved = cv::imwrite(filePath, frame, compression_params);
if (isSaved) {
qDebug() << "Image saved successfully!";
} else {
qDebug() << "Failed to save image!";
}
}
}
}
注意:
- 使用了
QFileDialog::getSaveFileName
来让用户选择保存图片的路径和文件名。 - 设置了图片保存时的压缩参数(这里以PNG格式为例,使用了PNG的压缩等级)。
- 需要将QString(Qt中的字符串类型)转换为std::string,因为OpenCV的
imwrite
函数使用的是C++标准库中的字符串类型。
7. 录像
实现录像功能稍微复杂一些,因为你需要将多帧图像保存到一个视频文件中。这通常涉及到使用OpenCV的VideoWriter
类。
首先,在你的MainWindow类中添加一个cv::VideoWriter
成员变量。
private:
// ...
cv::VideoWriter videoWriter;
// ...
然后,在适当的时机(比如点击一个“开始录像”按钮时)初始化这个VideoWriter对象,并在每一帧更新时写入视频。
// 假设这是你的开始录像槽函数
void MainWindow::on_startRecordingButton_clicked()
{
QString filePath = QFileDialog::getSaveFileName(this, "Save Video", "", "Video Files (*.avi)");
if (!filePath.isEmpty()) {
int codec = cv::VideoWriter::fourcc('X', 'V', 'I', 'D'); // 使用XVID编码器
double fps = 30.0; // 视频帧率
cv::Size frameSize(640, 480); // 视频帧大小
videoWriter.open(filePath.toStdString(), codec, fps, frameSize, true);
if (!videoWriter.isOpened()) {
qDebug() << "Failed to open video writer!";
return;
}
// 可以在这里设置一个标志位来表明正在录像
}
}
// 在updateFrame函数中写入视频帧
void MainWindow::updateFrame()
{
cv::Mat frame;
if (capture.read(frame)) {
// ...(转换颜色空间等操作)
// 如果正在录像,则写入帧
if (videoWriter.isOpened()) {
videoWriter.write(frame);
}
// 显示帧...
}
}
// 你还需要实现一个停止录像的槽函数来关闭VideoWriter
void MainWindow::on_stopRecordingButton_clicked()
{
if (videoWriter.isOpened()) {
videoWriter.release();
qDebug() << "Recording stopped and video saved.";
}
}
8. 源码示例
UI界面:
widget.h
private:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include<QFileDialog>
#include <QImage>
#include <QLabel>
#include <QDebug>
#include <QTimer>
#include <opencv2/opencv.hpp>
using namespace cv;
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_pushButton_clicked();
void on_pushButton_2_clicked();
void on_pushButton_3_clicked();
void on_pushButton_4_clicked();
void on_pushButton_5_clicked();
void on_pushButton_6_clicked();
void updateFrame();
private:
Ui::Widget *ui;
VideoCapture capture;
// QTimer timer;
VideoWriter videoWriter;
bool m_video_open;
bool m_video_record;
QTimer timer_open;
QTimer timer_record;
String recordViedo_fileName;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
m_video_open=false;
m_video_record=false;
// connect(&timer, &QTimer::timeout, this, &VideoRecordWidget::updateFrame);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
// 打开摄像头
capture = VideoCapture(0);
if (!capture.isOpened()) {
//qDebug("Failed to open camera.");
return;
}
//qDebug()<<"摄像头开启中";
// 开始定时器,以固定间隔刷新显示视频图像
timer_open.start(33); // 控制帧率为30fps
m_video_open=true;
connect(&timer_open, &QTimer::timeout, this, &Widget::updateFrame);
}
void Widget::on_pushButton_2_clicked()
{
// 关闭摄像头
//qDebug()<<"摄像头关闭中";
capture.release();
ui->label->clear();
ui->label->setText("视频录制器");
timer_open.stop();
m_video_open=false;
if(m_video_record){
//qDebug()<<"结束录制";
m_video_record=false;
timer_record.stop();
videoWriter.release();
}
}
void Widget::on_pushButton_3_clicked()
{
if(m_video_open){
if(videoWriter.isOpened()){
//qDebug()<<"已经有录制项目:"<<recordViedo_fileName<<"请先结束录制,再操作";
return;
}
// 获取当前时间作为视频文件名
std::time_t time = std::time(0);
std::ostringstream oss;
oss << "video_" << time << ".avi";
recordViedo_fileName=oss.str();
// std::string filename = oss.str();
ui->lineEdit->setText(recordViedo_fileName.c_str());
//qDebug()<<"摄像头开启中-并进行录制,文件名:"<<recordViedo_fileName;
timer_record.start(1000/25); // 控制帧率为30fps
m_video_record=true;
cv::Mat frame;
capture >> frame; // 从视频流中捕获当前帧
int codec = cv::VideoWriter::fourcc('M', 'J', 'P', 'G');
double fps = 25.0;
cv::Size frameSize(frame.cols, frame.rows);
// if(videoWriter.isOpened()){
// videoWriter.write(frame);
// return;
// }
videoWriter.open(recordViedo_fileName, codec, fps, frameSize);
connect(&timer_record, &QTimer::timeout, this, &Widget::updateFrame);
}else{
//qDebug()<<"请先打开摄像头";
}
}
void Widget::on_pushButton_4_clicked()
{
//qDebug()<<"暂停录制";
m_video_record=false;
}
void Widget::on_pushButton_5_clicked()
{
//qDebug()<<"继续录制";
m_video_record=true;
}
void Widget::on_pushButton_6_clicked()
{
//qDebug()<<"结束录制";
m_video_record=false;
timer_record.stop();
videoWriter.release();
}
void Widget::updateFrame()
{
if(m_video_open){
cv::Mat frame;
capture >> frame; // 从视频流中捕获当前帧
if (frame.empty()) {
return;
}
// 将OpenCV的Mat图像转换为Qt的QImage
cv::cvtColor(frame,frame,cv::COLOR_BGR2RGB);
QImage qimage(frame.data, frame.cols, frame.rows, static_cast<int>(frame.step), QImage::Format_RGB888);
QPixmap pixmap = QPixmap::fromImage(qimage);
// 设置QLabel显示图像
ui->label->setPixmap(pixmap.scaled(ui->label->size(), Qt::KeepAspectRatio));
if(m_video_record){
//qDebug()<<"录制中";
// 创建 VideoWriter 对象
// 检查是否成功打开视频文件
if (!videoWriter.isOpened())
{
//qDebug() << "无法打开视频文件.";
return;
}
cv::cvtColor(frame,frame,cv::COLOR_RGB2BGR);
videoWriter.write(frame);
}
}
}
运行结果:
9. 建议
1. 错误处理
在尝试打开摄像头、保存文件或进行视频写入时,可能会出现错误。确保你的应用程序能够优雅地处理这些错误,并向用户提供有用的反馈。
if (!capture.isOpened()) {
qDebug() << "Failed to open camera!";
// 可以显示一个错误消息框
QMessageBox::critical(this, "Error", "Failed to open camera.");
return;
}
if (!videoWriter.isOpened()) {
qDebug() << "Failed to open video writer!";
// 类似地,显示错误消息
QMessageBox::critical(this, "Error", "Failed to open video writer.");
return;
}
if (!cv::imwrite(filePath, frame, compression_params)) {
qDebug() << "Failed to save image!";
// 显示保存失败的消息
QMessageBox::warning(this, "Warning", "Failed to save image.");
}
2. 用户反馈
在执行重要操作时(如开始录像、停止录像、拍照等),向用户提供即时的反馈。这可以通过在UI中显示状态信息、更新按钮状态或弹出消息框来实现。
// 更新UI元素以反映录像状态
void MainWindow::on_startRecordingButton_clicked()
{
// ...(之前的代码)
ui->startRecordingButton->setEnabled(false); // 禁用开始按钮
ui->stopRecordingButton->setEnabled(true); // 启用停止按钮
// 可能还需要更新UI中的其他元素
}
void MainWindow::on_stopRecordingButton_clicked()
{
// ...(之前的代码)
ui->startRecordingButton->setEnabled(true); // 重新启用开始按钮
ui->stopRecordingButton->setEnabled(false); // 禁用停止按钮
// 更新UI以反映录像已停止
}
3. 资源管理
确保在应用程序关闭或用户执行某些操作时(如停止录像)正确释放摄像头和视频写入器等资源。
MainWindow::~MainWindow()
{
// 释放摄像头
capture.release();
// 如果视频写入器仍在打开状态,则关闭它
if (videoWriter.isOpened()) {
videoWriter.release();
}
// 其他资源清理...
}
// 也可以在停止录像的槽函数中关闭视频写入器
void MainWindow::on_stopRecordingButton_clicked()
{
// ...(之前的代码)
if (videoWriter.isOpened()) {
videoWriter.release();
}
}
4. 线程安全
如果你的摄像头捕获和UI更新是在不同的线程中进行的(这通常是一个好主意,以避免阻塞UI),请确保你的线程操作是安全的。Qt提供了多种机制来实现跨线程通信,如信号和槽、QThread、QMutex等。
5. 性能和优化
- 减少UI更新频率:如果你的应用程序正在以非常高的帧率捕获和显示视频帧,但你的显示器无法跟上这个速度,那么你可能想要限制UI更新的频率。
- 优化图像处理:在将帧发送到UI或保存到文件之前,尽量减少对帧的不必要处理。
- 使用硬件加速:如果可能的话,利用GPU进行图像处理,以提高性能。
6. 跨平台兼容性
确保你的应用程序在不同的操作系统和硬件配置上都能正常工作。特别是,注意不同操作系统之间的文件路径和编码差异。
通过遵循这些最佳实践,你可以创建出既健壮又用户友好的摄像头应用程序。