OpenCV高级编程——摄像头获取及录制技术(附DEMO示例))

引言

在这个博文中,我们将详细介绍如何使用Qt框架结合OpenCV库来实现一个摄像头操作的Demo。这个Demo将涵盖打开摄像头、显示视频流以及拍照和录像等功能。

一、准备工作

首先,确保你的开发环境中已经安装了Qt和OpenCV。Qt是一个跨平台的C++图形用户界面应用程序开发框架,而OpenCV是一个开源的计算机视觉和机器学习软件库。

安装Qt

  1. 访问Qt官方网站下载并安装Qt。
  2. 安装时确保包含了Qt Widgets模块,因为我们将使用QWidget来显示视频流。

安装OpenCV

  1. 你可以从OpenCV的官方网站下载源代码并编译,或者使用预编译的库。
  2. 确保将OpenCV的库文件添加到你的Qt项目的链接器路径中,并将头文件目录添加到包含路径中。

二、创建Qt项目

  1. 打开Qt Creator,选择“File” -> “New File or Project”。
  2. 选择“Qt Widgets Application”,然后点击“Choose…”。
  3. 为项目提供一个名称,选择合适的路径,然后点击“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. 跨平台兼容性

确保你的应用程序在不同的操作系统和硬件配置上都能正常工作。特别是,注意不同操作系统之间的文件路径和编码差异。

通过遵循这些最佳实践,你可以创建出既健壮又用户友好的摄像头应用程序。

  • 14
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

J^T

谢谢帅哥/美女

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值