OpenCV实现视频采集

一:Mat 图像容器

Mat是OpenCV 里装图像的 "盒子",将图像的像素数组 + 宽度 + 高度 + 格式信息全部打包在一起。

1.Mat 里存了什么?

一张图片 = 一堆像素点Mat 就是存这些像素:

  • cols:宽度
  • rows:高度
  • channels:通道数(1 = 灰度,3 = 彩色,4 = 带透明)
  • data:真正的像素数据(uchar*)
  • type:图像格式(CV_8UC1 / CV_8UC3...)

2.常用的Mat格式

1.CV_8UC1

8 位无符号,1 通道 → 灰度图

2.CV_8UC3

8 位无符号,3 通道 → 彩色图 注意:OpenCV 默认是 BGR 顺序

3.CV_8UC4

8 位无符号,4 通道 → BGRA 带透明

3.mat最常用的操作

1.imread 读取图像

cpp 复制代码
Mat img = imread("test.jpg");  // 读取图片
Mat frame;                     // 空图

2.判断空图

cpp 复制代码
if (img.empty()) {
    // 图片加载失败
}

3.获取宽,高,通道数

cpp 复制代码
int w = img.cols;
int h = img.rows;
int ch = img.channels();

4.复制图像

cpp 复制代码
Mat a = img.clone();   // 深拷贝(安全)
Mat b = img;           // 浅拷贝(共用数据)

5.imshow 显示图像

cpp 复制代码
Mat img = imread("test.jpg");
imshow("窗口标题", img);  // 显示
waitKey(0);               // 等待按键(必须写,不然一闪而过)

注意:只能在主线程中调用,子线程中不会生效。

6.imwrite 保存图像

cpp 复制代码
Mat frame;  // 你的图像

// 保存到当前目录
imwrite("保存.jpg", frame);

// 保存到指定路径
imwrite("/home/liu/图片/保存.png", frame);

二:图像颜色格式

0.转换函数

cpp 复制代码
// 源图像,目标图像,转换码
cvtColor(src, dst, 码表);

1.BGR(OpenCV默认)

  • 顺序:蓝 绿 红
  • 来源:OpenCV 天生就是 BGR
  • 用途:读图、摄像头、YOLO 推理
cpp 复制代码
Mat img = imread("a.jpg"); // 默认 BGR

2.BGRA(带透明通道)

  • 4 通道
  • BGR + 透明(Alpha)
  • 透明图片用(PNG)
  • 用途:贴图、水印、带透明的纹理
cpp 复制代码
cvtColor(mat, mat, COLOR_BGR2BGRA);

3.RGB(Qt/OpenGL/ 屏幕显示专用)

  • 3 通道
  • 顺序:红 绿 蓝
  • Qt、图片浏览器、屏幕 都用 RGB
  • 你必须转成 RGB 才能正常显示
cpp 复制代码
cvtColor(mat, mat, COLOR_BGR2RGB);

4.GRAY(灰度图)

  • 1 通道
  • 只有亮度,没有颜色
  • 黑→灰→白
  • 用途:去色、识别、压缩
cpp 复制代码
cvtColor(mat, mat, COLOR_BGR2GRAY);

5.HSV(颜色识别专用)

  • 3 通道:色调、饱和度、亮度
  • 用途:根据颜色找物体(红 / 黄 / 蓝物体跟踪)
  • 比 RGB/BGR 更适合识别颜色
cpp 复制代码
cvtColor(mat, mat, COLOR_BGR2HSV);

6./YUV(摄像头原始格式)

  • 摄像头输出最常见格式
  • Y = 亮度,UV = 颜色
  • 常见:YUYV、UYVY、NV12、NV21
  • 必须转 BGR/RGB 才能显示
cpp 复制代码
cvtColor(mat, mat, COLOR_YUV2BGR_YUYV);

7.综合对比表

格式 通道 特点 谁在用
BGR 3 OpenCV 默认 OpenCV、YOLO
BGRA 4 带透明 PNG、纹理
RGB 3 正常颜色 Qt、OpenGL、屏幕
GRAY 1 黑白 去色、识别
HSV 3 颜色识别 颜色跟踪
YUV 2/3 摄像头原始 摄像头、RTSP

三:Mat转换为QImage

Qt中界面显示一般都采用QImage容器,不能直接显示Mat容器,

Mat是OpenCV的图像格式,QImage是Qt的图像格式

Mat的颜色顺序是BGR,QImage的颜色顺序的RGB

cpp 复制代码
/**
 * @brief 万能 Mat 转 QImage(支持所有常见格式)
 * @param mat 输入 OpenCV Mat(支持 CV_8UC1 CV_8UC3 CV_8UC4)
 * @return 输出 Qt QImage(可直接显示/上传OpenGL)
 */
QImage matToQImage(const cv::Mat &mat)
{
    // 1. 空图直接返回空
    if (mat.empty())
        return QImage();

    // 2. 获取宽高
    int width = mat.cols;
    int height = mat.rows;

    // 3. 根据 Mat 类型转 QImage
    switch (mat.type()) {

    // ==========================
    // 灰度图 CV_8UC1
    // ==========================
    case CV_8UC1: {
        QImage img(mat.data, width, height, mat.step, QImage::Format_Grayscale8);
        return img.copy(); // 深拷贝,安全
    }

    // ==========================
    // 彩色图 BGR → RGB(最重要)
    // ==========================
    case CV_8UC3: {
        cv::Mat rgb;
        cv::cvtColor(mat, rgb, cv::COLOR_BGR2RGB); // 必须转!
        QImage img(rgb.data, width, height, rgb.step, QImage::Format_RGB888);
        return img.copy();
    }

    // ==========================
    // 带透明通道 BGRA → RGBA
    // ==========================
    case CV_8UC4: {
        QImage img(mat.data, width, height, mat.step, QImage::Format_RGBA8888);
        return img.copy();
    }

    // 不支持的格式
    default:
        return QImage();
    }
}

四:VideoCapture 视频捕获

VideoCapture = 视频 / 摄像头的 "读取器",支持:

1.打开摄像头

cpp 复制代码
cap.open(0);          //  0=默认摄像头

cap.open(-1);          //自动搜索

cap.open(0, cv::CAP_V4L2); //Linux下强制 V4L2 防止阻塞

2.本地视频

cpp 复制代码
cap.open("test.mp4"); // 视频

3.网络视频流

cpp 复制代码
cap.open("rtsp://..");// 网络流

4. 判断是否打开成功

cpp 复制代码
if (!cap.isOpened()) {
    qDebug() << "打开失败!";
}

5. 读一帧图像

cpp 复制代码
Mat frame;
cap.read(frame);

6.关闭摄像头

cpp 复制代码
// 关闭摄像头,释放硬件
cap.release();

7.读取宽,高,帧率

cpp 复制代码
// 宽度
int w = cap.get(CAP_PROP_FRAME_WIDTH);

// 高度
int h = cap.get(CAP_PROP_FRAME_HEIGHT);

// 帧率
double fps = cap.get(CAP_PROP_FPS);

8.摄像头线程标准写法

cpp 复制代码
void CameraThread::run()
{
    VideoCapture cap;
    cap.open(0);  // 打开摄像头

    if (!cap.isOpened()) {
        qDebug() << "摄像头打开失败!";
        return;
    }

    Mat frame;
    while (m_running)
    {
        cap.read(frame);
        if (frame.empty()) continue;

        // 发给界面显示
        emit frameReady(frame);

        msleep(30);//精准 30fps
    }

    cap.release();
}

为什么不采用定时器捕获?

① 定时器精度差:QTimer 最小只能到 15ms

② 定时器依附主线程事件循环:界面一滑动、绘图、加载东西,定时器直接被卡住,延迟

③ 无法做到真正实时:摄像头是连续不断输出数据定时器是间隔性去拿→ 必然丢帧

五:简易视频播放器(QLabel版本+OpenGL版本)

1.效果图

显示图片

显示本地视频

打开本地摄像头

打开网络视频流

以下是几个可用测试地址(可能有延迟,打开后需要等几秒钟):

rtmp://mobliestream.c3tv.com:554/live/goodtv.sdp

rtmp://ns8.indexforce.com/home/mystream

rtmp://ns8.indexforce.com/home/mystream

2.整体思路

1.现在设计师界面拖入一个QLabel和一个QOpenGLWidget,在下方按钮添加几个想要的功能。

QOpenGLWdiget需要提升至一个类种用于实现画面渲染。

2.捕获视频或网络流在子线程中进行,然后确定多少帧率取一次视频帧,将这个视频帧通过信号槽传递给主线程中进行画面的更新,打开图片可直接在主线程中传递到对应控件上显示即可,主线程专门复制渲染或者绘制(事件循环)。

3.OpenCV模块工程文件配置:

cpp 复制代码
# 自动找 OpenCV
find_package(OpenCV REQUIRED)

# 包含头文件
include_directories(${OpenCV_INCLUDE_DIRS})

#链接库
target_link_libraries(项目名 PRIVATE ${OpenCV_LIBS})

3.核心部分代码

1.捕获线程:

cpp 复制代码
#ifndef CAPTURETHREAD_H
#define CAPTURETHREAD_H

#include <QThread>
#include <QImage>
#include <QTimer>

//捕获线程支持的数据源类型
enum class CapType
{
    camera,//相机
    localvideo,//本地视频
    urlVideo//网络视频流
};

class CaptureThread : public QThread
{
    Q_OBJECT
public:
    CaptureThread(CapType type);
    void run()override;
public slots:
    void ExitCap();
    void SetSource(QString source);//图片,视频,url
signals:
    void SendImg(QImage img);
private:
    CapType m_capType;
    bool m_running;
    QString m_source;
};

#endif // CAPTURETHREAD_H
cpp 复制代码
#include "CaptureThread.h"
#include <opencv4/opencv2/opencv.hpp>
#include <QDebug>
using namespace cv;
/**
 * @brief 万能 Mat 转 QImage(支持所有常见格式)
 * @param mat 输入 OpenCV Mat(支持 CV_8UC1 CV_8UC3 CV_8UC4)
 * @return 输出 Qt QImage(可直接显示/上传OpenGL)
 */

QImage matToQImage(const cv::Mat &mat)
{
    // 1. 空图直接返回空
    if (mat.empty())
    {
        return QImage();
    }
    // 2. 获取宽高
    int width = mat.cols;
    int height = mat.rows;

    // 3. 根据 Mat 类型转 QImage
    switch (mat.type()) {

    case CV_8UC1: {
        QImage img(mat.data, width, height, mat.step, QImage::Format_Grayscale8);
        return img.copy(); // 深拷贝,安全
    }

    case CV_8UC3: {
        cv::Mat rgb;
        cv::cvtColor(mat, rgb, cv::COLOR_BGR2RGB); // 必须转!
        QImage img(rgb.data, width, height, rgb.step, QImage::Format_RGB888);
        return img.copy();
    }
    case CV_8UC4: {
        QImage img(mat.data, width, height, mat.step, QImage::Format_RGBA8888);
        return img.copy();
    }

    default:
        return QImage();
    }
}

CaptureThread::CaptureThread(CapType type):
    m_running(true),
    m_capType(type)
{
}

void CaptureThread::SetSource(QString source)
{
    m_source=source;
}

void CaptureThread::ExitCap()
{
    m_running=false;
}
void CaptureThread::run()
{
    VideoCapture cap;
    if(m_capType==CapType::camera)
    {
        cap.open(0);
    }
    else
    {
        cap.open(m_source.toStdString());
    }
    if(!cap.isOpened())
    {
        qDebug()<<"打开失败";
        return;
    }
    Mat frame;
    while(m_running)
    {
        cap.read(frame);
        if(frame.empty())//没有数据了,退出
        {
            break;
        }
        emit SendImg(matToQImage(frame));
        msleep(30);//30fps
    }
    cap.release();
}

2.GLWidget渲染部分:

cpp 复制代码
#ifndef VIDEOWIDGET_H
#define VIDEOWIDGET_H

#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>

#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QOpenGLBuffer>
#include <QOpenGLVertexArrayObject>
class VideoWidget : public QOpenGLWidget,private QOpenGLFunctions_3_3_Core
{
    Q_OBJECT
public:
    VideoWidget(QWidget *parent=nullptr);
    ~VideoWidget();
    void initializeGL();
    void resizeGL(int w,int h);
    void paintGL();
public slots:
    void updateFrame(QImage img);
private:
    void initShader();
    QOpenGLShaderProgram *m_shader;

    void initTexture();
    QOpenGLTexture* m_texture;

    void initContainer();
    QOpenGLBuffer *m_vbo;
    QOpenGLVertexArrayObject *m_vao;
};

#endif // VIDEOWIDGET_H
cpp 复制代码
#include "VideoWidget.h"
#include <QImage>
VideoWidget::VideoWidget(QWidget *parent):QOpenGLWidget(parent) {}
VideoWidget::~VideoWidget()
{}
void VideoWidget::initializeGL()
{
    initializeOpenGLFunctions();
    initShader();
    initTexture();
    initContainer();
}
void VideoWidget::resizeGL(int w,int h)
{
}
void VideoWidget::paintGL()
{
    glClearColor(1.0,1.0,1.0,1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    m_shader->bind();
    m_vao->bind();
    m_texture->bind();

    glDrawArrays(GL_TRIANGLE_FAN,0,4);
}

void VideoWidget::initShader()
{
    const char* vertexShader=R"(
        #version 330 core
        layout(location=0) in vec2 aPos;
        layout(location=1) in vec2 aTexCoord;
        out vec2 texCoord;
        void main()
        {
            gl_Position=vec4(aPos,0.0,1.0);
            texCoord=aTexCoord;
        }
    )";

    const char* fragmentShader=R"(
        #version 330 core
        in vec2 texCoord;
        out vec4 fragColor;
        uniform sampler2D tex;
        void main()
        {
            fragColor=texture(tex,texCoord);
        }
    )";

    m_shader=new QOpenGLShaderProgram(this);
    m_shader->addShaderFromSourceCode(QOpenGLShader::Vertex,vertexShader);
    m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment,fragmentShader);
    m_shader->link();
}
void VideoWidget::initTexture()
{
    m_texture=new QOpenGLTexture(QOpenGLTexture::Target2D);
    m_texture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
    m_texture->setMagnificationFilter(QOpenGLTexture::Linear);
    m_texture->setWrapMode(QOpenGLTexture::ClampToEdge);
}
void VideoWidget::updateFrame(QImage img)
{
    //如果图像尺寸变了,必须销毁旧纹理
    if (m_texture && (m_texture->width() != img.width() || m_texture->height() != img.height()))
    {
        qDebug() << "调整尺寸";
        m_texture->destroy();
        delete m_texture;
        m_texture = nullptr;
    }

    // 纹理不存在则重新创建
    if (!m_texture)
    {
        m_texture = new QOpenGLTexture(QOpenGLTexture::Target2D);
        m_texture->create();
        m_texture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
        m_texture->setMagnificationFilter(QOpenGLTexture::Linear);
        m_texture->setWrapMode(QOpenGLTexture::ClampToEdge);
    }

    // 直接更新数据,不再设置格式/大小
    m_texture->setData(img.mirrored(false, true));

    update();
}
void VideoWidget::initContainer()
{
    float vertex[]={
        -1.0,1.0,   0.0,1.0,
        1.0,1.0,    1.0,1.0,
        1.0,-1.0,   1.0,0.0,
        -1.0,-1.0,  0.0,0.0
    };

    m_vao=new QOpenGLVertexArrayObject(this);
    m_vao->create();
    m_vao->bind();
    m_vbo=new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
    m_vbo->create();
    m_vbo->bind();
    m_vbo->allocate(vertex,sizeof(vertex));
    glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,sizeof(float)*4,(void*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,sizeof(float)*4,(void*)(sizeof(float)*2));
    glEnableVertexAttribArray(1);
    m_vbo->release();
    m_vao->release();
}

3.更新图片帧槽函数:

cpp 复制代码
void Widget::showFrame(QImage img)
{
    if (img.isNull()) return;
    ui->lblVideo->setPixmap(QPixmap::fromImage(img).scaled(ui->lblVideo->size(), Qt::KeepAspectRatio));//QLabel显示
    ui->openGLWidget->updateFrame(img);//OpenGL 显示
}
相关推荐
@fai2 小时前
【Python多线程截图】当 Python 多线程遇上底层 C 库——一次由“串图”引发的线程安全深度思考
python·opencv·numpy
alvin_20053 小时前
python之OpenGL应用(五)变换
python·opengl
sali-tec3 小时前
C# 基于OpenCv的视觉工作流-章35-组件连通
图像处理·人工智能·opencv·算法·计算机视觉
V搜xhliang02465 小时前
VLA 模型微调与 ROS 2 集成
人工智能·深度学习·计算机视觉·自然语言处理·知识图谱
做cv的小昊5 小时前
【Video Agent】(Arxiv2601,Meta)Agentic Very Long Video Understanding
论文阅读·计算机视觉·语言模型·音视频·openai·论文笔记·视频理解
TEC_INO7 小时前
Linux44:opencv在H264码流中添加LOGO
人工智能·opencv·计算机视觉
纤纡.7 小时前
OpenCV 实战:从视频处理到图像轮廓检测的全维度解析
人工智能·opencv·音视频
AI浩7 小时前
Hybrid-SORT:弱线索对于在线多目标跟踪的重要性
人工智能·计算机视觉·目标跟踪
V搜xhliang02467 小时前
多模态数据采集与标注
人工智能·目标检测·计算机视觉·知识图谱